目录
1 创建工程
1.1 新工程
1.2 编辑器布局
1.3 Package包
1.4 颜色空间
1.5 示例场景
2 构建一个简单的时钟
2.1 创建物体对象
2.2 创建表盘
2.3 创建时钟部件
2.4 12个小时指示器
2.5 创建指针
3 时钟动起来
3.1 C# 脚本资产
3.2 定义组件类型
3.3 持有一个指针
3.4 获取所有的指针
3.5 苏醒
3.6 通过代码旋转
3.7 获取当前时间
3.8 旋转指针
3.9 指针动画
3.10 持续旋转
本文重点内容: 1、构建时钟 2、写C#脚本 3、旋转时钟指针来展示时间 4、指针动画
这是有关学习使用Unity的基础知识的系列教程中的第一篇。在其中,我们将创建一个简单的时钟并对程序进行编程,以使其显示当前时间。你不需要具有Unity编辑器的任何经验,但是假定你一般具有多窗口编辑器应用程序的经验。
在我大部分的教程的底部,你都将找到指向教程许可证,包含完成的教程项目的存储库以及教程页面的PDF版本的链接。
本教程是CatLikeCoding系列的一部分,原文地址见文章底部。
本教程使用Unity 2019.4.10f1制作。
(是时候创建时钟了)
1 创建工程
在开始使用Unity编辑器之前,我们需要首先创建一个项目。
1.1 新工程
当你打开Unity时,将显示Unity Hub。这是一个启动程序和安装程序应用程序,你可以在其中创建或打开项目,安装Unity版本以及执行其他操作。如果你尚未安装Unity 2019.4或更高版本,请立即添加。
哪些Unity版本合适? Unity每年发布多个新版本。有两个并行的发布时间表。最稳定和安全的是LTS版本,它表示长期支持,对于Unity来说,这是两年。我的教程坚持使用LTS版本,其中最新版本为2019.4。本教程专门使用2019.4.10。版本号的第三部分表示补丁程序发行。修补程序版本包含错误修复,只有很少的新功能。另一个f1后缀表示正式的最终版本。本教程将使用任何2019.4版本。
偶尔我的教程包含小问题和他们的答案,总是在一个灰色的框,就像上面的一个。在网页上,答案默认是隐藏的。这可以通过点击或轻敲问题来切换。
创建新项目时,可以选择其Unity版本和模板。我们将使用标准的3D模板。创建之后,它将添加到项目列表中,并在相应版本的Unity编辑器中打开。
是否可以使用其他渲染管线创建项目? 可以,唯一的区别是该项目在其默认场景中将包含更多内容,并且你的材质将有所不同。你的项目还将包含适当的程序包。
1.2 编辑器布局
如果尚未自定义编辑器,则最终将获得其默认窗口布局。
(默认的窗口布局)
默认布局包含我们需要的所有窗口,但是你可以通过对窗口重新排序和分组来根据需要自定义它。你也可以打开和关闭窗口,例如资产商店。每个窗口还具有其自己的配置选项,可通过其右上角的三点按钮进行访问。除此之外,大多数还具有带有更多选项的工具栏。如果你的窗口看起来与教程中的窗口不同(例如,场景窗口具有统一的背景而不是天空盒),则其选项之一就不同。
可以通过Unity编辑器右上方的下拉菜单切换到预配置的布局。还可以在此处保存当前布局,以便稍后还原。
1.3 Package包
Unity的功能是模块化的。除了核心功能外,还可以下载单独的软件包并将其包含在项目中。默认情况下,默认的3D项目当前默认包含一些软件包,你可以在项目窗口的Packages下看到这些软件包。
(默认的包)
通过切换项目窗口右上方的按钮来隐藏这些程序包,该按钮看起来像是带有短划线的眼睛。这纯粹是为了减少编辑器中的视觉混乱。该按钮还显示有多少个这样的软件包。
可以通过程序包管理器来控制项目中包含哪些程序包,可以通过Windows/Package Manager”菜单项将其打开。
(包管理器,仅显示项目中的包)
本教程不需要任何包含的软件包,因此我将其全部删除。最简单的方法是使用工具栏将包列表限制为仅在项目中。然后一次选择一个软件包,然后使用右下角的Remove按钮。每次删除后,Unity都会重新编译,因此该过程需要几秒钟的时间。
如果你使用的代码编辑器具有集成包,则不要删除相关的包,也不要在以后添加它。例如,如果你使用的是Visual Studio Code,则可以使用Visual Studio Code Editor包以获得更好的体验。
1.4 颜色空间
如今,渲染通常是在线性色彩空间中完成的,但是Unity默认仍然使用gamma色彩空间。为了获得最佳视觉效果,请通过Edit/ Project Settings... 打开项目设置窗口,然后选择Player类别并向下滚动到Other Settings面板中的Rendering部分。确保将色彩空间设置为线性。
(颜色空间设置为线性)
是否有理由使用伽玛色彩空间? 仅当您针对旧硬件或旧图形API时。OpenGL ES 2.0和WebGL 1.0不支持线性空间,此外,在旧的移动设备上,伽玛比线性空间快。
1.5 示例场景
新项目包含一个名为SampleScene的示例场景,默认情况下会打开该场景。你可以在项目窗口的Assets / Scenes下找到其资产。
(project窗口下的示例场景)
默认情况下,项目窗口使用两列布局。你可以通过其三点配置菜单选项切换到单列布局。
(单列布局)
示例场景包含一个主摄像机和一个方向光。这些是游戏对象。它们被列在场景下的层级窗口中。
(对象层次窗口)
你可以通过层次结构窗口或场景窗口选择游戏对象。相机具有一个场景图标,看起来像老式的胶片相机,而定向光的图标看起来像太阳。
(场景窗口下的Icon)
如何浏览场景窗口? 可以结合使用alt键或Option键和光标来旋转视图。还可以使用箭头键移动视点,并通过滚动进行缩放。同样,按F键可将视图聚焦在当前选定的对象上。还有更多可能性,但是这些足以在现场找到自己的方式。
选中对象后,有关该对象的详细信息将显示在检查器窗口中,但是在需要时我们将进行介绍。我们不需要修改摄像机或灯光,因此可以通过在层次结构窗口中单击它们左侧的眼睛图标(将鼠标悬停在此处时出现)来将它们隐藏在场景中。这只是为了减少场景窗口中的视觉混乱。
(隐藏对象)
眼睛旁边的手状图标有什么作用? 这样就无法通过场景窗口选择游戏对象,同时又保持可见。
2 构建一个简单的时钟
现在我们的项目已正确设置,可以开始创建时钟了。
2.1 创建物体对象
我们需要一个游戏对象来代表时钟。将从最简单的游戏对象开始,它是一个空对象。可以通过GameObject/ Create Empty菜单选项创建。或者,你可以在层次结构窗口的上下文菜单中使用Create Empty选项,可以用另一种单击方式将其打开,通常是右键单击或双击。这会将游戏对象添加到场景中。它是可见的,并立即在SampleScene下的层次结构窗口中选中,该窗口现在标有星号,表示它尚未保存更改。
只要选中游戏对象,检查器窗口就会显示其详细信息。它的顶部是带有对象名称的标题,以及一些配置选项。默认情况下,对象是启用的,不是静态的,没有标签,位于默认层上。这些设置可以使用,但名称除外。将其重命名为Clock。
(选中clock之后,检视窗口的显示)
Title下方是游戏对象所有组件的列表。该列表始终在顶部有一个Transform组件,这是我们当前所有的时钟。它控制游戏对象的位置,旋转和比例。确保所有时钟的位置和旋转值都设置为0。其缩放应统一为1。
2D对象呢? 使用2D而不是3D时,可以忽略三个尺寸之一。专门用于2D的对象(如UI元素)通常具有RectTransform,这是专门的Transform组件。
由于游戏对象为空,因此在场景窗口本身中不可见。但是,在游戏对象位于世界中心的位置可以看到操纵工具。
(选中移动工具)
可以通过编辑器工具栏左上方的按钮来控制哪个操作工具处于活动状态。也可以通过Q,W,E,R,T和Y键激活这些模式。默认情况下,移动工具处于活动状态。
(工具栏)
模式按钮旁边还有三个按钮,用于控制操作工具的放置,方向和对齐。
2.2 创建表盘
尽管我们有一个时钟对象,但是我们什么都看不到。需要在其中添加3D模型,才能渲染这些内容。Unity包含一些原始对象,我们可以使用它们来构建简单的时钟。首先,通过GameObject/ 3D Object / Cylinder将一个圆柱体添加到场景中。确保它具有与我们的时钟相同的Transform值。
(GameObject代表一个圆柱体)
为什么我的圆柱体看起来更暗? 如果圆柱体的阴影面为纯黑色,可能是环境照明的automatic generation被关闭了。要启用它,请通过Window/ Rendering / Lighting Settings打开照明窗口,然后打开Scene页签底部的Auto Generate开关。
新对象比空的游戏对象多三个组成部分。首先,它具有一个MeshFilter,其中包含对内置圆柱体网格的引用。
(MeshFilter组件)
第二个是MeshRenderer。该组件用于确保渲染对象的网格。它还确定用于渲染的材质,这是默认材质。该材料还将显示在检查器的组件列表下方。
(MeshRenderer 组件 默认的材质)
第三是CapsuleCollider,用于3D物理。该对象代表圆柱体,但它具有胶囊状的碰撞体,因为Unity没有原始圆柱体碰撞体。我们不需要它,因此可以删除此组件。如果你想在时钟上使用物理原理,最好使用MeshCollider组件。可以通过右上角的三点下拉菜单删除组件。
(不带碰撞的圆柱体)
通过压平,可以将圆柱体变成钟表的表面。这是通过减小其比例尺的Y分量来完成的。减少到0.2。由于圆柱网格的高度为2个单位,因此其有效高度为0.4个单位。我们做一个大时钟,因此将其比例尺的X和Z分量增加到10。
(缩放后的时钟)
我们的时钟本可以竖立或悬挂在墙上,但其表面目前处于水平状态。我们可以通过将圆柱体旋转四分之一圈来解决此问题。在Unity中,X轴指向右,Y轴指向上方,Z轴指向前方。因此,让我们在设计时钟时要牢记相同的方向,这意味着当我们沿Z轴查看时钟时会看到其正面。将圆柱体的X旋转设置为90,并调整场景视图,以使时钟的front部分可见。
(旋转圆柱体)
将圆柱对象的名称更改为Face,因为它代表时钟的面。它只是时钟的一部分,因此我们将其作为Clock对象的子对象。为此,我们将Face拖到层次结构窗口中的时钟上。
(face的子节点)
子对象服从其父对象的转换。这意味着当时钟改变位置时,face也会改变。就好像它们是一个单一的实体。旋转和缩放也是如此。你可以使用它来创建复杂的对象层次结构。
2.3 创建时钟部件
钟面的外圈通常带有标记,以帮助指示其显示的时间。这被称为时钟外围部件。让我们使用小块来指示12小时制的小时数。
通过GameObject/ 3D Object / Cube将一个立方体对象添加到场景中,将其命名为Hour Indicator 12,并将其作为Clock的子节点。子对象在层次结构中的顺序无关紧要,你可以将其放置在Face上方或下方。
(Hour indicator子节点)
将其X比例设置为0.5,将Y比例设置为1,将Z比例设置为0.1,以使其成为一个狭窄的扁平长块。然后将其X位置设置为0,Y位置设置为4,Z位置设置为-0.25。它将其放置在face上方以指示12点。同时删除其BoxCollider组件。
(12小时指示器)
该指示器很难看到,因为它的颜色与Face相同。通过Assets/ Create / Material或通过项目窗口的加号按钮或上下文菜单为其创建单独的材质。这为我们提供了与默认材质重复的资产。将其名称更改为Hour Indicator。
(project 窗口下的Hour indicator,1列和2列的布局对比)
选择材质并将其Albedo更改为其他颜色,方法是单击其颜色字段。这将打开一个颜色弹出窗口,其中提供了多种选择颜色的方法。我选择了深灰色,对应于十六进制494949,与RGB 0-255模式的均匀73相同。我们不使用alpha通道,因此其值无关紧要。我们还可以保留所有其他材质属性。
(深灰色)
什么是反照率? 反照率是一个拉丁词,意为白色。当被白光照射时,它就是某种东西的颜色。
使Hour indicator使用此材质。你可以通过将材质拖到场景或层次结构窗口中的对象上来执行此操作。也可以将其拖动到检查器窗口的底部,或更改其MeshRenderer的Materials数组的Element 0。
(深色的Hour indicator)
2.4 12个小时指示器
钟表有12个小时,可以使用同一个指示符,现在我们为每小时添加一个指示符。首先让场景视图摄像机定向,这样我们就可以沿着Z轴向下看。你可以通过点击场景视图右上方的视图摄像机小装置的轴锥来完成。也可以通过网格工具栏按钮改变场景网格的轴为Z。
(沿Z轴直视时钟)
复制12个Hour Indicator游戏对象。可以通过Edit / Duplicate完成,也可以通过键盘快捷键或层次结构窗口中的上下文菜单来执行此操作。副本将显示在层次结构窗口中原始文档的下方,也是Clock的子级。名称Hour Indicator12(1)。将其重命名为Hour Indicator 6,并取反其位置的Y分量,使其指示小时6。
(小时12和小时6)
用同样的方法为第3小时和第9小时创建指示符。在这种情况下,它们的X位置应该是4和4,而它们的Y位置应该是0
(4个小时指示器)
然后创建另一个Hour Indicator12的副本,这次要做的是小时1。将其X位置设置为2,将Y位置设置为3.464,将Z旋转设置为-30。然后将其复制为小时2,交换其X和Y位置,并将其Z旋转加倍至-60。
(小时1和小时2)
这些数字从哪里来的? 每个小时沿Z轴顺时针旋转30°。在这种情况下,我们使用负旋转,因为Unity的旋转是逆时针方向。然后通过三角函数找到小时1的位置。sin30等于1/2,而cos为√3/2。我们用小时指示器与中心的距离来衡量,也就是4,就是√3/2*4 ≈3.464。对于第2小时,旋转角度为60°,为此,我们可以简单地将正弦和余弦互换。
复制这两个指示器,并否定它们的Y位置和旋转来创建第4小时和第5小时的指示器。然后在第1、2、4、5小时使用相同的技巧来创建剩余的指标,这一次否定它们的X位置,再次否定它们的旋转。
(所有的小时指示器)
2.5 创建指针
下一步是制作时钟的指针。我们从时针开始。再次复制小时指示器12,并将其命名为Hours Arm。然后创建一个Clock Arm材质,并让时针使用它。在本例中,我将它设为纯黑色,十六进制000000。将时针的X刻度减少到0.3,Y刻度增加到2.5。然后改变它的X位置为0,Y位置为0.75,所以它指向第12小时,但也有点相反的方向。当它旋转起来的时候,会使得它看起来好像有一个小平衡力。
(时针)
时针臂必须绕时钟的中心旋转,但是改变其Z旋转会使其绕自己的中心旋转。
(时针沿着自己的中心旋转)
发生这种情况是因为旋转是相对于游戏对象的本地位置。为了创建适当的旋转,我们需要引入一个pivot对象,然后旋转该对象。因此,创建一个新的空游戏对象并将其作为Clock的节点。你可以通过在层次结构窗口中通过Clock的上下文菜单创建对象来直接执行此操作。将其命名为Hours Arm Pivot,并确保其位置和旋转为零且比例尺一致为1。然后使Hours Arm成为pivot的子节点。
(时针和中心点)
现在尝试旋转pivot。如果通过场景视图执行此操作,请确保将工具手柄位置模式设置为Pivot而不是Center。
(时针沿着中心点旋转)
复制Hours Arm Pivor两次以创建Minutes Arm Pivot和Seconds Arm Pivot。相应地重命名它们,包括重复的arm子对象。
(所有的指针层次)
Minutes Arm应该比Hours Arm窄且更长,因此将其X比例设置为0.2,将Y比例设置为4,然后将其Y位置增加到1。还要将其Z位置更改为-0.35,使其位于小时臂的顶部。请注意,这适用于手臂,而不是其枢轴。
(调整分针的Transform)
调整秒针。这次将XY标度使用0.1和5,将YZ位置使用1.25和-0.45。
(调整秒针的Transform)
让我们通过为它创建单独的材质来使秒针与众不同。给它一个深红色,十六进制的B30000。另外,在完成时钟构建后,我关闭了场景窗口中的网格。
(3个完整指针)
那么现在是通过File / Save或指示的键盘快捷键保存场景的好时机。
保持项目资产的有序性也是一个好习惯。由于我们拥有三种材质,因此将它们放在通过Assets/ Create / Folder或通过项目窗口创建的Material文件夹中。然后可以将材质拖到那里。
(Project窗口下的Material 文件夹)
3 时钟动起来
目前,我们的时钟无法显示时间,它总是停留在十二点钟。要对其进行动画处理,我们需要为其添加自定义行为。为此,我们创建了一个通过脚本定义的自定义组件类型。
3.1 C# 脚本资产
通过Assets/ Create / C#脚本将新脚本资产添加到项目中,并将其命名为Clock。C#是用于Unity脚本的编程语言,发音为C-sharp。让我们立即将其放置在新的Scripts文件夹中,以保持项目整洁。
(Clock脚本)
选择脚本后,检查器将显示其内容。但是要编辑代码,我们需要使用代码编辑器。你可以通过按脚本检查器中的Open... 按钮或在层次结构窗口中双击脚本来打开脚本进行编辑。可以通过Unity的首选项配置打开哪个程序。
3.2 定义组件类型
将脚本加载到代码编辑器中之后,首先删除标准模板代码,因为我们将从头开始创建组件类型。
空文件是不会定义任何内容的。它需要包含我们时钟组件的定义。我们要定义的不是组件的单个实例。相反,我们定义了称为Clock的通用类或类型。一旦建立,我们就可以在Unity中创建多个这样的组件。
在C#中,我们首先声明要定义一个类,然后定义其名称,从而定义Clock类型。在下面的代码片段中,更改的代码具有黄色背景。我们从一个空文件开始,它的内容应按字面意义归类为Clock类,不过你可以随意在单词之间添加空格和换行符。
什么是class? 可以将类视为可用于创建驻留在计算机内存中的对象的蓝图。蓝图定义了这些对象包含哪些数据以及它们具有什么功能。
我们将使用其中的一些,但Clock不会使用。
因为我们不想限制哪个代码可以访问我们的Clock类型,所以最好在它前面加上public access修饰符。
类的默认访问修饰符是什么? 没有access修饰的话符,就会被当做一个内部类。这将限制从同一程序集的代码访问,当你使用打包在单独程序集中的代码时,这将变得很重要。为确保其始终有效,默认情况下将类设为public。
目前,我们还没有有效的C#语法。如果要保存文件并返回到Unity编辑器,则编译错误将记录在其控制台窗口中。
编译器指出我们正在定义一个类型,因此我们实际上必须定义它的类型。这是通过声明后的代码块完成的。代码块的边界用大括号表示。我们暂时将其保留为空,因此只需编写{}。
我们的代码现已生效。保存文件,然后切换回Unity。Unity编辑器将检测到脚本资产已更改,并触发重新编译。完成之后,选择脚本。检查员将通知我们该资产不包含MonoBehaviour脚本。
(非组件脚本)
这意味着我们不能使用此脚本在Unity中创建组件。至此,我们的Clock定义了一种基本的C#对象类型。我们的自定义组件类型必须继承Unity的MonoBehaviour类型,并继承其数据和功能。
mono-behavior是什么意思? 这表示我们可以对自己的组件进行编程,以向游戏对象添加自定义行为。这就是behavior部分所指的意思。但这是使用英式拼写就很奇怪。mono部分是指将对自定义代码的支持添加到Unity的方式。它使用了Mono项目,该项目是.NET框架的多平台实现。因此,MonoBehaviour。这是一个旧名称,由于向后兼容,我们一直持续使用。
要将Clock转换为MonoBehaviour的子类型,我们需要更改类型声明,以使其继承自该类型,这是在类型名称后加冒号,然后再继承其内容。这使Clock继承MonoBehaviour类类型的所有内容。
但是,这将导致编译后出现错误。编译器抱怨它找不到MonoBehaviour类型。发生这种情况是因为类型包含在名称空间中,该名称空间是UnityEngine。要访问它,我们必须使用其标准名称UnityEngine.MonoBehaviour。
什么是命名空间? 命名空间类似于网站域,但用于代码。就像域可以具有子域一样,名称空间也可以具有子命名空间。最大的不同是它是用相反的方式编写的。因此,它不是forum.unity.com,而是com.unity.forum。命名空间用于组织代码并防止名称冲突。 Unity自带了包含UnityEngine代码的程序集,你不需要单独在网上获取它。代码编辑器使用的项目文件应该自动设置以识别它。
访问Unity类型时总是必须包含UnityEngine前缀是不方便的。幸运的是,我们可以声明应自动搜索名称空间以完成C#文件中的类型名称。这可以通过使用UnityEngine添加来完成;在文件的顶部。需要用分号标记语句的结尾。
现在,我们可以将自定义组件添加到Unity中的Clock游戏对象中。可以通过将脚本资产拖动到对象上,也可以通过对象检查器底部的Add Component 按钮来完成。
(Clock 游戏对象 以及Clock组件)
请注意,我的教程中的大多数代码类型都链接到在线文档。例如,MonoBehaviour是一个链接,可带你进入该类型的Unity在线脚本API页面。(译注:这部分被译者吃掉了,翻译文档里没提供,对链接太麻烦了。。。)
3.3 持有一个指针
要旋转指针,Clock对象需要了解它们。我们从时针开始。像所有游戏对象一样,可以通过调整其Transform组件来旋转它。因此,我们需要向Clock添加关于中心点的Transform组件的相关信息。这可以通过在其代码块内添加数据字段来完成,该数据字段定义为名称后跟分号。
hours pivot比较合适作为字段名字。然而,名字必须是单个的单词。惯例是将字段名的第一个单词小写,其他单词大写,然后将它们连接在一起。我们把它命名为hoursPivot。
using语句去了哪里? 它仍然在那里,我只是没有展示。这些代码片段将包含足够的现有代码,目的是为了突出更改的上下文。
我们还必须声明字段的类型,在这种情况下为UnityEngine.Transform。它必须写在字段名称的前面。
现在,我们的类定义了一个字段,该字段可以保存对另一个对象的引用,该对象的类型必须为Transform。我们需要确保它引用了hours arm pivot的Transform组件。
默认情况下,字段是私有的,这意味着它们只能由属于Clock的代码访问。但是该类不了解我们的Unity场景,因此没有直接的方法将字段与正确的对象相关联。我们可以通过将字段声明为可序列化来更改它。这意味着当Unity保存场景时,应该将其包含在场景的数据中,这是通过将所有数据按顺序(序列化)并将其写入文件来实现的。
将一个字段标记为可序列化是通过将属性附加到该字段(在本例中为SerializeField)来完成的。它写在方括号之间的字段声明的前面,通常在其上方的一行上,但也可以放在同一行上。
可以将其设置为Public吗? 可以,但是公开公开访问类字段通常是不好的形式。经验法则是仅在其他类型的C#代码需要访问类内容时才公开类内容,然后优先于字段使用方法或属性。越难访问的东西越容易维护,因为可以直接依赖它的代码更少。在本教程中,我们唯一的C#代码是Clock,因此没有理由公开其内容。
字段可序列化后,Unity将对其进行检测并将其显示在Clock游戏对象的Clock组件的检查器窗口中。
(Hours pivot字段)
为了建立正确的连接,将Hours Arm Pivot从层次结构拖动到Hours Pivot字段。或者,使用该字段右侧的圆形按钮,然后在弹出的列表中搜索Pivot。在这两种情况下,Unity编辑器都会获取Hours Arm Pivot的Transform组件并将其引用放入我们的领域。
(Hours pivot链接上了)
3.4 获取所有的指针
我们也要对分针,秒针的pivot做同样的操作。因此,添加两个可序列化的转换字段来使用适当的名称计时。
可以使这些字段声明更加简洁,因为它们共享相同的属性,访问修饰符和类型。可以在属性和类型声明之后将它们合并为以逗号分隔的字段名称列表。
//有什么用? 双斜杠表示注释。编译器将忽略它们直到行尾的所有文本。如果需要,它用于添加文本以解释代码。我还使用它来指示已删除的代码。除此之外,已删除的代码还有一行。
在编辑器中链接另外两个指针。
(所有的pivot都被链接了)
3.5 苏醒
现在我们可以接触到指针了,下一步就是旋转它们。为此,我们需要告诉Clock执行一些代码。这是通过在类中添加一个代码块(称为方法)来完成的。该块必须以一个名称作为前缀,该名称按惯例大写。我们将其命名为Awake,建议在组件唤醒时执行代码。
方法有点像数学函数,例如f(x)=2x 3。该函数接受一个由变量参数x表示的数字,然后将其翻倍,然后添加3。它作用于一个数,它的结果也是一个数。对于一个方法,它更像是f (p)=c,其中p表示输入参数,而c表示它执行的任何代码。
像数学函数一样,方法可以产生结果,但这不是必需的。我们必须声明结果的类型(就好像它是一个字段一样),或者写空来表明没有结果。在我们的例子中,我们只想执行一些代码而不提供结果值,因此我们使用后者。
我们也不需要任何输入数据。但是,我们仍然必须将方法的参数定义为圆括号之间的逗号分隔列表。在我们的情况下,这只是一个空列表。
现在,我们有了一个有效的方法,尽管它还没有做任何事情。就像Unity检测到我们的字段一样,它也检测到此Awake方法。当组件具有Awake方法时,Unity在唤醒时将在该组件上调用该方法。这是在播放模式下创建或加载后发生的。我们目前处于编辑模式,因此还没有发生调用。
Awake不是必须Public吗? Awake和一些其他的方法集合被视为特殊的Unity事件方法。无论我们如何声明它们,Unity引擎都会找到它们并在适当的时候调用它们。这是从托管的.NET环境外部发生的。
请注意,Awake和其他特殊的Unity事件方法在我的教程中均以粗体显示,并链接到其在线Unity脚本API页面。
3.6 通过代码旋转
要旋转指针,我们需要创建一个新的rotation。可以通过为Transform的localRotation属性分配一个新值来更改其旋转方式。
什么是属性? 属性是一种伪装成字段的方法。它可能是只读或只写的。C#约定是大写属性,但是Unity的代码没有这样做。
尽管Transform组件的旋转是在检查器中以欧拉角/每轴度数定义的,但是在代码中,我们需要使用四元数来进行旋转。
什么是四元数? 四元数基于复数,用于表示3D旋转。尽管比单独的X,Y和Z旋转角度的组合更难理解,但它们具有一些有用的特性。例如,他们不会遭受万向节锁定(gimbal lock)的困扰。
我们可以通过调用Quaternion.Euler方法基于欧拉角创建四元数。为此,请在Awake中编写,然后以分号结束语句。
该方法具有用于描述所需旋转的参数。在这种情况下,我们将在方法名称之后提供一个逗号分隔的列表,其中包含三个参数,所有参数都放在圆括号中。我们为X,Y和Z旋转提供三个数字。前两个使用零,Z旋转使用-30。
调用的结果是一个四元数结构值,该值包含围绕Z轴顺时针旋转30°,与我们的时钟的小时1相匹配。
什么是结构体? 结构(structure的缩写)是一个蓝图,就像一个类一样。区别在于它创建的任何内容均被视为简单值(例如整数或颜色),而不是对象。它没有身份感(sense of identity)。定义自己的结构与定义类的作用相同,只不过你写的是struct而不是class。
要将旋转应用于小时臂,请使用=分配语句将Quaternion.Euler的结果分配给hourPivots.localRotation。
localRotation和旋转之间有什么区别? localRotation属性单独表示由Transform组件描述的旋转,因此是相对于其父级的旋转。这是你在检查器中看到的旋转。相反,rotation属性表示世界空间中的最终旋转,同时考虑了整个对象层次。如果将时钟整体旋转,则设置该属性会产生奇怪的结果,因为指针会忽略该属性,因为该属性会补偿时钟的旋转。
Unity完成重新编译后,你会在控制台中看到一条警告,抱怨Clock.hoursPivot从未分配值给它。现在仅显示警告,因为这是我们第一次通过代码访问该字段。触发警告是因为C#编译器不知道应该通过检查器进行连接。我们可以通过为字段声明分配默认值来消除此警告,我们可以通过立即为其分配默认值来做到这一点。
现在,在编辑器中进入播放模式。你可以通过Edit/ Play,指示的键盘快捷键或按编辑器窗口顶部中央的Play按钮来执行此操作。Unity将把焦点切换到游戏窗口,该窗口将渲染场景中主摄像机看到的内容。时钟组件将被唤醒,并且时钟将被设置为1点。
(在播放模式下总是1点)
如果相机未聚焦在时钟上,则可以移动它以使时钟可见,但请记住,退出播放模式时会重置场景,因此在播放模式下对场景所做的任何更改都不会持久 。不过,对于资产而言并非如此,对资产的更改始终会持续存在。在播放模式下,你还可以打开场景窗口,甚至可以打开多个场景和游戏窗口。继续之前退出播放模式。
3.7 获取当前时间
下一步是弄清楚我们是何时苏醒的。要访问正在运行的计算机的系统时间,可以使用DateTime结构。这不是Unity类型,可以在System名称空间中找到。它是.NET框架核心功能的一部分,这是Unity用于支持脚本编写的功能。
DateTime具有Now属性,该属性产生包含当前系统日期和时间的DateTime值。要检查它是否正确,我们将在Awake开始时将其记录到控制台。可以通过将其传递给Debug.Log方法来实现。
现在,每次进入播放模式时,我们都会记录一个时间戳。
3.8 旋转指针
我们马上就能让时钟工作了。从小时开始, DateTime具有Hour属性,该属性使我们获得DateTime值的小时部分。在当前时间戳上调用它会给我们一天中的时间。
要使小时显示当前小时,我们需要将−30°旋转乘以当前小时。乘法以*字符完成。我们也不再需要记录当前时间,因此可以去掉该声明。
(当前时间为4点)
为了清楚地表明我们正在从小时转换为度,可以定义一个包含转换因子的hoursToDegrees字段。Quaternion.Euler的角度定义为浮点值,因此我们将使用float类型。因为我们已经知道编号,所以我们可以立即将其分配为字段声明的一部分。然后乘以该字段而不是Awake中的文字-30。
什么是浮点? 计算机不能存储所有数字,它们必须在二进制存储器中可表示,二进制存储器由0或1的位组成。这使得无法在有限的存储器大小(例如⅓)内精确地存储许多数字,就像我们不能 用十进制符号精确地写那个数字。我们能做的最好的就是写0.3333333并在某个时候停止。
为了提高小值的精度,我们添加一个单独的指数,它表示数值的数量级。然后
可以表示除以100,而不会丢失有意义的数字。我们也可以用
来表示100的乘法,同时在小数点前只保留一个数字。 浮点数在计算机上的工作方式相同,不同之处在于它们使用二进制而不是十进制数字,并且还必须表示特殊值,例如无穷大和非数字。浮点数就是这样的值,它存储在四个字节中,这意味着它具有32位。
如果我们声明一个没有后缀的整数,则假定它是一个整数,这是一个不同的值类型。尽管编译器会自动将它们转换,但通过向它们添加f后缀,我们可以使我们清楚所有数字均为float类型。
每小时度数始终相同。我们可以通过在hoursToDegrees声明中添加const前缀来强制执行此操作。这将其变成一个常量而不是一个字段。
const值有什么特别之处? const关键字指示值永远不会改变,并且不必是字段。取而代之的是,它将在编译期间计算其值,并替换该常量的所有用法。这仅适用于基本类型(例如数字)。
让我们使用DateTime的适当属性对其他两个手臂进行相同的处理。一分钟和一秒都由负六度旋转表示。当我们现在还访问其他两个字段时,我们也应该显式地给它们提供默认值,以避免编译器警告我们之前得到了。
(当前时间5:16:31)
我们使用DateTime.Now三次,分别获取小时,分钟和秒。每次我们再次遍历该属性时,这都需要做一些额外工作,从理论上讲,这可能会导致不同的时间值。为确保不会发生这种情况,我们应该只检索一次时间。为此,我们可以在方法内部声明一个变量并为其分配时间,然后再使用该值。让我们命名为time。
什么是变量? 变量的作用类似于字段,只是它仅在执行方法时存在。它属于方法,而不是类。
如果是变量,则可以省略类型声明,而用var关键字替换。这样可以缩短代码,但只有在声明变量时可以从分配给变量的类型中推断出变量的类型时才有可能。另外,只有在语句中明确提到类型时,我才这样做,在这里就是这种情况。
3.9 指针动画
进入播放模式时,我们会获得当前时间,但之后时钟保持静止。为了使时钟与当前时间保持同步,请将Awake方法的名称更改为Update。这是另一种特殊的事件方法,只要我们处于播放模式,Unity就会在每一帧而不是一次调用该方法。
(时钟更新)
什么是帧? 在播放模式下,Unity会从主摄像机的角度连续渲染场景。渲染完成后,结果将显示在显示器上。然后显示屏将显示该帧,直到获得下一帧为止。在渲染新帧之前,所有内容都会更新。因此,Unity经历了一系列的更新,渲染,更新,渲染等等。通常,将单个更新步骤随后渲染一次场景视为一个帧,尽管实际上,时间安排更为复杂。
请注意,我们的Clock组件在检查器中的名称前面获得了一个切换开关。这使我们可以禁用它,从而阻止Unity调用其Update方法。
(clock组件可以被禁用)
3.10 持续旋转
时钟的指针精确地指示当前的小时,分钟或秒。它的行为就像一个数字时钟,离散但带有指针。通常,时钟具有旋转缓慢的指针,可提供时间的模拟表示。让我们改变方法,使我们的时钟变成模拟时钟。
DateTime不包含分数数据。幸运的是,它确实具有TimeOfDay属性。这给我们提供了一个TimeSpan值,该值通过其TotalHours,TotalMinutes和TotalSeconds属性包含所需格式的数据。
首先从DateTime.Now获取TimeOfDay结构值,并将其存储在变量中。由于此语句中未提及TimeSpan类型,因此我将使变量的类型明确。然后调整用于旋转手臂的属性。
这将导致编译器错误,警告我们无法从double转换为float。发生这种情况是因为TimeSpan属性产生的值具有双精度浮点类型,即double。这些值比浮点值提供更高的精度,但是Unity的代码仅适用于单精度浮点值。
单精度够吗? 对于大多数游戏,够了。当使用非常大的距离或比例差异时,这将成为一个问题。然后,你必须应用远距传送或相对于相机的渲染之类的技巧,以使活动区域保持在世界原点附近。尽管使用双精度可以解决此问题,但也会使所涉及数字的内存大小加倍,从而导致其他性能问题。游戏引擎通常使用单精度浮点值,GPU也是如此。
我们可以通过从double转换为float来解决此问题。此过程称为转换,是通过在要转换的值前面的圆括号内写入新类型来完成的。
(模拟时钟)
现在,你已经知道了在Unity中创建对象和编写代码的基础。下一个教程是构建视图。
欢迎扫描二维码,查看更多精彩内容。点击 阅读原文 可以跳转原教程。
本文翻译自 Jasper Flick的系列教程
原文地址:
https://catlikecoding.com/unity/tutorials