找回密码
 立即注册
查看: 324|回复: 0

Unity基础 01_游戏对象和脚本

[复制链接]
发表于 2022-11-29 15:18 | 显示全部楼层 |阅读模式
原文地址:Game Objects and Scripts
创建一个时钟

Packages:

  • Unity的功能是模块化的。除了核心功能之外,还有额外的软件包可以下载并包含在您的项目中。默认情况下,默认3D项目当前包含一些包,您可以在项目窗口中的“包”下看到这些包



Default packages


  • 通过切换项目窗口右上角的按钮,可以隐藏这些包,该按钮看起来像一只眼睛,上面有一条破折号。这纯粹是为了减少编辑器中的视觉混乱,包仍然是项目的一部分。该按钮还显示有多少这样的包。 您可以通过包管理器控制项目中包含哪些包,包管理器可以通过窗口/包管理器菜单项打开



Package manager, only showing packages in project.


  • 这些软件包为Unity增加了额外的功能。例如,Visual Studio编辑器为用于编写代码的Visual Studio编辑器添加了集成。本教程不使用包含的包的功能,所以我把它们都删除了。唯一的例外是Visual Studio编辑器,因为这是我用来编写代码的编辑器。如果您使用不同的编辑器,您会希望包含它的集成包,如果它存在的话
Color Space:

  • 现在渲染通常在线性色彩空间中完成,但是Unity仍然默认使用gamma色彩空间。为了获得最佳视觉效果,请选择“项目设置”窗口的“播放器”类别,打开“其他设置”面板,并向下滚动到其“渲染”部分。确保色彩空间设置为线性。Unity会显示警告,这可能需要很长时间,但是对于一个几乎空的项目来说就不会这样了。确认切换



Color space set to linear


  • 仅当您针对旧硬件或旧图形API时。OpenGL ES 2.0和WebGL 1.0不支持线性空间,除此之外,gamma在旧的移动设备上可能比线性更快
Creating a Game Object

  • 我们需要一个游戏对象来表示时钟。我们将从最简单的游戏对象开始,它是一个空的对象。它可以通过游戏对象/创建空菜单选项来创建。或者,您可以使用“层次”窗口的快捷菜单中的“创建空”选项,您可以通过另一种单击方式打开该窗口,通常是右键单击或双指点击。这将游戏对象添加到场景中。它是可见的,并立即在“层次”窗口中的SampleScene下被选中,现在用星号标记,表示它有未保存的更改。您也可以立即更改它的名称或留待以后



Hierarchy with new game object selected.


  • 只要游戏对象被选中,检查器窗口就会显示它的详细信息。它的顶部是一个标题,带有对象的名称和一些配置选项。默认情况下,对象处于启用状态,不是静态的,没有标记,并且位于默认图层上。这些设置都没问题,除了它的名字。将其重命名为Clock



Inspector window with clock selected


  • 标题下面是游戏对象所有组件的列表。列表顶部总是有一个转换组件,这是我们的时钟目前所拥有的。它控制游戏对象的位置、旋转和缩放。确保所有时钟的位置和旋转值都设置为0。其比例尺应统一为1
  • 因为游戏对象是空的,所以它在场景窗口中是不可见的。然而,操纵工具在游戏对象的位置是可见的,它在世界的中心



Selected with move tool.


  • 可以通过编辑器工具栏左上角的按钮来控制哪个操纵工具处于活动状态。这些模式也可以通过Q、W、E、R、T和Y键激活。该组中最右边的按钮用于启用自定义编辑器工具,这是我们没有的。默认情况下,移动工具处于活动状态



Manipulation mode toolbar.


  • 模式按钮旁边还有三个按钮,用于控制操纵工具的放置、方向和捕捉
Creating the Face of the Clock:

  • 虽然我们有一个时钟对象,但我们还看不到任何东西。我们将不得不添加三维模型,所以得到一些渲染。Unity包含一些原始对象,我们可以用它们来构建一个简单的时钟。让我们开始通过游戏对象/ 3D对象/圆柱体添加一个圆柱体到场景中。确保它与我们的时钟具有相同的变换值



代表圆柱体的游戏对象


  • 这个新对象比一个空的游戏对象多了三个组件。首先,它有一个MeshFilter,其中包含对内置圆柱体网格的引用



MeshFilter component, set to cylinder.


  • 第二个是网格渲染器。该组件的目的是确保对象的网格得到渲染。它还决定了用于渲染的材质,即默认材质。该材质也显示在检查器中,位于组件列表下方



MeshRenderer组件,设置为默认材质。


  • 第三个是用于3D物理的CapsuleCollider。该对象代表一个圆柱体,但它有一个胶囊碰撞器,因为Unity没有原始的圆柱体碰撞器。我们不需要,可以去掉这个组件。如果你想在时钟上使用物理学,你最好使用MeshCollider组件。组件可以通过右上角的三点下拉菜单删除
  • 我们将把圆柱体变平,变成钟的表面。这是通过减少其标度的Y分量来实现的。降低到0.2。由于圆柱体网格高两个单位,其有效高度变为0.4个单位。我们再做一个大钟,把它刻度的X和Z分量增加到10



Scaled cylinder.


  • 将圆柱体对象的名称更改为Face,因为它代表时钟的表面。它只是时钟的一部分,所以我们让它成为时钟对象的子对象。我们通过在层次窗口中把脸拖到时钟上来实现



Face child object.


  • 子对象受制于其父对象的变换。这意味着当时钟改变位置时,脸也跟着改变。就好像他们是一个单一的实体。旋转和缩放也是如此。您可以使用它来创建复杂的对象层次
Creating the Clock Periphery

  • 钟面的外环通常有标记,有助于显示时间。这就是所谓的时钟外围。让我们用块来表示12小时制的小时。 通过GameObject / 3D Object / Cube向场景中添加一个立方体对象,将其命名为小时指示器12,并使其成为时钟的子对象。子对象在层次中的顺序无关紧要,您可以将它放在面的上方或下方



Hour indicator child object.


  • 将其X比例设置为0.5,Y比例设置为1,Z比例设置为0.1,使其成为一个窄而平的长块。然后将其X位置设为0,Y位置设为4,Z位置设为0.25。这就把它放在了脸的上方来指示12小时。还要移除它的BoxCollider组件



Indicator for hour 12.


  • 指示器很难看到,因为它和脸的颜色一样。让我们为它创建一个单独的材质,通过Assets / Create / Material,或者通过加号按钮或项目窗口的上下文菜单。这为我们提供了一个材质资产,它是默认材质的副本。将其名称改为小时指示器



Hour indicator in project window, one and two column layout.


  • What is albedo:Albedo是一个拉丁词,意思是白色。它是被白光照亮的东西的颜色



Hours arm.


  • 手臂必须围绕时钟中心旋转,但改变其Z旋转会使其围绕自己的中心旋转



Clock arm rotates around its center.


  • 发生这种情况是因为旋转是相对于游戏对象的局部位置。为了创建适当的旋转,我们必须引入一个枢轴对象并旋转它。因此,创建一个新的空游戏对象,并使其成为Clock的子对象。您可以通过层次窗口中Clock的上下文菜单直接创建对象。将其命名为小时臂枢轴,并确保其位置和旋转为零,其比例一致为1。然后使小时臂成为支点的子



Clock arm rotates around the pivot.



最终结果

时钟动画


  • 我们的钟现在不报时,它总是停在12点。要制作动画,我们必须给它添加一个自定义行为。我们通过创建一个定制的组件类型来实现这一点,它是通过一个脚本来定义的。
C# Script Asset:

  • 通过Assets / Create / C# Script向项目添加一个新的脚本资源,并将其命名为Clock。C#是用于Unity脚本的编程语言,发音为C-sharp。让我们也立即把它放在一个新的脚本文件夹中,以保持项目的整洁



Scripts folder with Clock script, one and two column layout.


  • 当脚本被选中时,检查器将显示其内容。但是要编辑代码,我们必须使用代码编辑器。您可以通过按“打开”来打开脚本进行编辑...按钮,或者在“层次结构”窗口中连按它。打开哪个程序可以通过Unity的偏好设置进行配置



Inspector of C# Clock asset.

Defining a Component Type:

  • 一旦脚本加载到代码编辑器中,首先删除标准模板代码,因为我们将从头开始创建组件类型。
  • 空文件不定义任何内容。它必须包含我们的时钟组件的定义。我们要定义的不是一个组件的单个实例。相反,我们定义了称为Clock的一般类或类型。一旦建立起来,我们就可以在Unity中创建多个这样的组件,尽管在本教程中我们只限于使用一个时钟
  • 在C#中,我们通过首先声明我们正在定义一个类,然后是它的名字来定义时钟类型。在下面的代码片段中,修改后的代码有一个黄色的背景,或者如果你使用黑色的网页主题来查看本教程的话,是暗红色的。当我们从一个空文件开始时,它的内容实际上应该变成class Clock而不是别的,尽管您可以随意在单词之间添加空格和换行符
class Clock

  • 你可以把一个类想象成一个蓝图,用来创建驻留在计算机内存中的对象。蓝图定义了这些对象包含什么数据以及它们有什么功能。 类也可以定义不属于对象实例而是属于类本身的数据和功能。这通常用于提供全局可用的功能。我们将使用其中的一些,但时钟不会有它
  • 因为我们不想限制哪个代码可以访问我们的时钟类型,所以最好在它前面加上public access修饰符。
public class Clock

  • 此时,我们还没有有效的C#语法。如果你要保存文件并返回到Unity编辑器,那么编译错误将会记录在控制台窗口中。 我们指出我们正在定义一个类型,所以我们必须实际定义它是什么样的。这是由声明后面的代码块完成的。代码块的边界用花括号表示。我们暂时将其留空,因此只需填写{}
public class Clock {}

  • 我们的代码现在有效。保存文件并切换回Unity。Unity编辑器将检测到脚本资源已经改变,并触发重新编译。完成后,选择我们的脚本。检查员将通知我们该资产不包含MonoBehaviour脚本



Non-component script.


  • 这意味着我们不能使用这个脚本在Unity中创建组件。此时,我们的时钟定义了一个基本的C#对象类型。我们的自定义组件类型必须扩展Unity的MonoBehaviour类型,继承它的数据和功能
  • What does mono-behavior mean? 这个想法是,我们可以编写自己的组件来为游戏对象添加自定义行为。行为部分指的就是这个。它只是碰巧使用了英国拼法,这很奇怪。mono部分指的是对自定义代码的支持被添加到Unity的方式。它使用了Mono项目,这是。NET框架。因此,MonoBehaviour。这是一个古老的名字,由于向后兼容,我们一直沿用它。
  • 要将Clock转换成MonoBehaviour的子类型,我们必须更改我们的类型声明,以便它扩展该类型,这是通过在我们的类型名后加上冒号,后跟它扩展的内容来实现的。这使得Clock继承了MonoBehaviour类类型的所有内容
public class Clock : MonoBehaviour {}

  • 但是,这将导致编译后出错。编译器报错说找不到MonoBehaviour类型。发生这种情况是因为该类型包含在一个命名空间中,该命名空间是UnityEngine。要访问它,我们必须使用它的完全限定名UnityEngine.MonoBehaviour
What's a namespace?

  • 命名空间就像一个网站域,但对于代码来说。就像域可以有子域一样,名称空间可以有子名称空间。最大的区别是它是反过来写的。因此,它将不是com.unity.forum,而是com.unity.forum。名称空间用于组织代码和防止名称冲突。 包含UnityEngine代码的程序集是Unity自带的,你不用去网上单独取。如果您导入了适当的编辑器集成包,代码编辑器使用的项目文件应该被设置为自动识别它
  • 当访问Unity类型时,总是必须包含UnityEngine前缀是不方便的。幸运的是,我们可以声明应该自动搜索命名空间来完成C#文件中的类型名。这是通过使用UnityEngine添加来完成的;在文件的顶部。分号是标记语句结束所必需的
using UnityEngine;

public class Clock : MonoBehaviour {}

  • 现在,我们可以在Unity中将自定义组件添加到时钟游戏对象中。这可以通过将脚本资源拖到对象上,或者通过对象检查器底部的“添加组件”按钮来完成



Clock game object with our Clock component.


  • 请注意,我的教程中的大多数代码类型都链接到了在线文档。例如,MonoBehaviour是一个链接,带你到Unity的在线脚本API页面
Getting Hold of an Arm:

  • 为了旋转手臂,时钟对象需要知道它们。让我们从时针开始。像所有游戏对象一样,它可以通过调整其变换组件来旋转。因此,我们必须将手臂枢轴的变换组件的知识添加到Clock中。这可以通过在其代码块中添加一个数据字段来实现,定义为一个名称后跟一个分号
  • hours pivot这个名称适合于该字段。然而,名字必须是单个单词。惯例是将字段名的第一个单词小写,其他所有单词大写,然后将它们粘在一起。所以我们将它命名为hoursPivot
public class Clock : MonoBehaviour {

        hoursPivot;
}

  • 我们还必须声明字段的类型,在本例中是UnityEngine.Transform,它必须写在字段名的前面。
Transform hoursPivot;

  • 我们的类现在定义了一个字段,它可以保存对另一个对象的引用,该对象的类型必须是Transform。我们必须确保它包含对小时臂轴心的变换组件的引用。 默认情况下,字段是私有的,这意味着它们只能由属于Clock的代码访问。但是这个类不知道我们的Unity场景,所以没有直接的方法将这个字段与正确的对象关联起来
  • 我们可以通过将该字段声明为serializable来改变这种情况。这意味着当Unity保存场景时,它应该包含在场景的数据中,这是通过将所有数据放入一个序列(序列化)并将其写入一个文件来实现的
  • 将字段标记为可序列化是通过向其附加属性来完成的,在本例中为SerializeField。它写在方括号内的字段声明的前面,通常在字段声明的上一行,但也可以放在同一行
[SerializeField]
Transform hoursPivot;

  • 一旦该字段是可序列化的,Unity将检测到这一点,并将其显示在我们的时钟游戏对象的时钟组件的检查器窗口中



Hours pivot field.


  • 要建立正确的连接,请将小时臂透视从层次结构拖到小时透视字段上。或者,使用字段右侧的圆形按钮,在弹出的列表中搜索枢轴。在这两种情况下,Unity编辑器都会获取小时臂轴心的变换组件,并在我们的字段中引用它



Hours pivot connected.

Knowing all Three Arms:

  • 我们必须对分秒臂枢轴做同样的事情。因此,使用适当的名称向Clock添加两个可序列化的转换字段
[SerializeField]
Transform hoursPivot,Transform minutesPivot,Transform secondsPivot;       
Waking Up:

  • 现在我们已经可以使用臂枢轴,下一步是旋转它们。为此,我们需要告诉Clock执行一些代码。这是通过向类中添加一个代码块(称为方法)来实现的。块必须以一个名称为前缀,这个名称按照惯例是大写的。我们将它命名为Awake,这意味着代码应该在组件唤醒时执行。
public class Clock : MonoBehaviour {

        [SerializeField]
        Transform hoursPivot, minutesPivot, secondsPivot;

        Awake {   }
}

  • 例如,方法有点像数学函数 f(x)=2x+3。该函数采用一个数字——由变量参数表示 x—加倍,然后加三。它对一个数字进行运算,其结果也是一个数字。就方法而言,它更像是 f(p)=c其中 p代表输入参数,并且  c代表它执行的任何代码
  • 像数学函数一样,方法可以产生结果,但这不是必需的。我们必须声明结果的类型——就像它是一个字段一样——或者编写void来表示没有结果。在我们的例子中,我们只想执行一些代码而不提供结果值,所以我们使用void。
  • 我们也不需要任何输入数据。然而,我们仍然必须将方法的参数定义为圆括号之间的逗号分隔列表。在我们的例子中它只是一个空列表。
void Awake () {}Rotating via Code:

  • 要旋转手臂,我们必须创建一个新的旋转。我们可以通过为它的 localRotation 属性指定一个新的属性来改变转换的旋转。
  • What's a property ? 属性是伪装成字段的方法。它可能是只读的或只写的。C#的惯例是大写属性,但是Unity的代码没有这样做。
  • 尽管在检查器中,变换组件的旋转是用欧拉角(每轴度数)定义的,但在代码中,我们必须用四元数来完成
  • What's a quaternion ? 四元数基于复数,用于表示3D旋转。虽然比单独的X、Y和Z旋转角度的组合更难理解,但它们有一些有用的特征。例如,它们不会遭受万向节锁
        void Awake () {
                Quaternion.Euler;
        }

  • 该方法具有用于描述所需旋转的参数。在这种情况下,我们将在方法名后提供一个包含三个参数的逗号分隔列表,所有参数都在圆括号内。我们为X、Y和Z旋转提供三个数字。前两次旋转使用0,Z旋转使用 -30
Quaternion.Euler(0, 0, -30);

  • 该调用的结果是一个四元数结构值,包含绕Z轴顺时针旋转30°,与时钟上的1小时相匹配。
  • struct—structure:的缩写——是一个蓝图,就像类一样。不同之处在于,它创建的任何东西都被视为一个简单的值,如整数或颜色,而不是一个对象。它没有认同感。定义你自己的结构和定义一个类是一样的,除了你写struct而不是class。
  • 要将此旋转应用到时臂,请指定四元数的结果。Euler到hoursPivots.localRotation,使用=赋值语句
hoursPivot.localRotation = Quaternion.Euler(0, 0, -30);What's the difference between localRotation and rotation?

  • localRotation属性表示由Transform组件单独描述的旋转,因此它是相对于其父级的旋转。这是你在它的检查器中看到的旋转。相比之下,rotation属性表示世界空间中的最终旋转,将整个对象层次考虑在内。如果我们将时钟作为一个整体旋转,设置该属性会产生奇怪的结果,因为手臂会忽略它,因为该属性会补偿时钟的旋转。
  • 现在在编辑器中进入播放模式。您可以通过编辑/播放(指示的键盘快捷键)或按下编辑器窗口顶部中间的播放按钮来完成此操作。Unity会将焦点切换到游戏窗口,渲染场景中主摄像头看到的内容。时钟组件将被唤醒,时钟将被设置为一点钟



Always one o'clock in play mode.


  • 如果相机没有聚焦在时钟上,您可以移动它,使时钟变得可见,但请记住,退出播放模式时场景会重置,因此在播放模式下对场景所做的任何更改都不会持续。但是这对于资产来说是不正确的,对它们的改变总是持久的。你也可以在游戏模式下打开场景窗口,甚至多个场景和游戏窗口。继续之前退出播放模式
Getting the Current Time

  • 下一步是计算出我们醒来的当前时间。我们可以使用 DateTime 结构来访问正在运行的设备的系统时间。DateTime不是Unity类型,它位于系统命名空间中。它是的核心功能的一部分。NET框架,Unity用它来支持脚本。
  • DateTime有一个Now属性,该属性生成一个包含当前系统日期和时间的DateTime值。为了检查它是否正确,我们将在唤醒开始时将其记录到控制台。我们可以通过将它传递给调试器来做到这一点。日志方法
using System;
using UnityEngine;

public class Clock : MonoBehaviour {

        [SerializeField]
        Transform hoursPivot, minutesPivot, secondsPivot;

        void Awake () {
                Debug.Log(DateTime.Now);
                hoursPivot.localRotation = Quaternion.Euler(0, 0, -30);
        }
}

  • 现在,我们每次进入播放模式时都会记录一个时间戳。您可以在控制台窗口和编辑器窗口底部的状态栏中看到它
Rotating the Arms

  • 我们正在接近一个工作时钟。让我们再次从小时开始。DateTime有一个小时属性,用于获取DateTime值的小时部分。在当前时间戳上调用它将会给我们一天中的某个小时。
Debug.Log(DateTime.Now.Hour);

  • 因此,要让时针显示当前小时,我们必须将30°旋转乘以当前小时。乘法是用星号*字符完成的。我们也不再需要记录当前时间,因此可以删除该语句
hoursPivot.localRotation = Quaternion.Euler(0, 0, -30 * DateTime.Now.Hour);


Currently four o'clock in play mode.


  • 为了清楚地表明我们正在将小时转换为度,我们可以定义一个包含转换因子的hours to degrees字段。四元数的角度。欧拉被定义为浮点值,所以我们将使用浮点类型。因为我们已经知道了这个数字,所以我们可以立即将它作为字段声明的一部分进行赋值。然后乘以字段,而不是Awake中的文字-30。
        float hoursToDegrees = -30;

        [SerializeField]
        Transform hoursPivot, minutesPivot, secondsPivot;

        void Awake () {
                hoursPivot.localRotation =
                        Quaternion.Euler(0, 0, hoursToDegrees * DateTime.Now.Hour);
        }

  • 如果我们声明一个不带后缀的整数,那么它被假定为一个整数,这是一个不同的值类型。虽然编译器会自动转换它们,但是让我们通过给它们加上f后缀来明确所有的数字都是float类型
        float hoursToDegrees = -30f;

        [SerializeField]
        Transform hoursPivot, minutesPivot, secondsPivot;

        void Awake () {
                hoursPivot.localRotation =
                        Quaternion.Euler(0f, 0f, hoursToDegrees * DateTime.Now.Hour);
        }

  • 每小时的度数总是不变的。我们可以通过在hoursToDegrees的声明中添加const前缀来实现这一点。这将它变成一个常数,而不是一个字段
const float hoursToDegrees = -30f;

  • 让我们使用DateTime的适当属性对其他两个分支进行同样的处理。分和秒都用负六度的旋转来表示
        const float hoursToDegrees = -30f, minutesToDegrees = -6f, secondsToDegrees = -6f;

        [SerializeField]
        Transform hoursPivot, minutesPivot, secondsPivot;

        void Awake () {
                hoursPivot.localRotation =
                        Quaternion.Euler(0f, 0f, hoursToDegrees * DateTime.Now.Hour);
                minutesPivot.localRotation =
                        Quaternion.Euler(0f, 0f, minutesToDegrees * DateTime.Now.Minute);
                secondsPivot.localRotation =
                        Quaternion.Euler(0f, 0f, secondsToDegrees * DateTime.Now.Second);
        }



Currently 5:16:31.


  • 我们使用日期时间。现在三次,检索小时、分钟和秒。每次我们再次遍历属性,这需要一些工作,理论上可能会导致不同的时间值。为了确保这种情况不会发生,我们应该只检索一次时间。我们可以通过在方法中声明一个变量并给它分配时间,然后使用这个值来实现。让我们把它命名为时间
  • What's a variable ? 变量的作用类似于字段,只是它只在方法执行时存在。它属于方法,而不是类
        void Awake () {
                DateTime time = DateTime.Now;
                hoursPivot.localRotation =
                        Quaternion.Euler(0f, 0f, hoursToDegrees * time.Hour);
                minutesPivot.localRotation =
                        Quaternion.Euler(0f, 0f, minutesToDegrees * time.Minute);
                secondsPivot.localRotation =
                        Quaternion.Euler(0f, 0f, secondsToDegrees * time.Second);
        }

  • 在变量的情况下,可以省略类型声明,用var关键字替换它。这可以缩短代码,但只有当变量的类型可以从声明时赋予它的内容中推断出来时才是可能的。此外,我更喜欢只在语句中明确提到类型时才这样做,这里就是这种情况
var time = DateTime.Now;
Animating the Arms:

  • 当进入播放模式时,我们得到当前时间,但之后时钟保持不动。为了保持时钟与当前时间同步,将我们的Awake方法的名称改为Update。这是另一个特殊的事件方法,只要我们处于播放模式,Unity每一帧都会调用它,而不是只调用一次
void Update () {
        var time = DateTime.Now;
        hoursPivot.localRotation =
                Quaternion.Euler(0f, 0f, hoursToDegrees * time.Hour);
        minutesPivot.localRotation =
                Quaternion.Euler(0f, 0f, minutesToDegrees * time.Minute);
        secondsPivot.localRotation =
                Quaternion.Euler(0f, 0f, secondsToDegrees * time.Second);
        }
What's a frame?

  • 在游戏模式下,Unity会从主摄像机的角度连续渲染场景。一旦绘制完成,结果就呈现给显示器。显示器然后将显示该帧,直到它得到下一帧。在渲染一个新的帧之前,所有的东西都会被更新。所以Unity经历了一系列的更新、渲染、更新、渲染等等。一次渲染场景之后的单个更新步骤通常被认为是单个帧,尽管实际上时间安排要复杂得多



Clock component that can be disabled.

Continuously Rotating:

  • 我们时钟的指针准确地指示当前的小时、分钟或秒钟。它的行为就像一个数字时钟,离散,但有手臂。典型的时钟有缓慢旋转的指针,提供时间的模拟表示。让我们改变我们的方法,使我们的时钟成为模拟的。
  • 日期时间不包含小数数据。幸运的是,它确实有一个TimeOfDay属性。这通过其TotalHours、TotalMinutes和TotalSeconds属性为我们提供了一个TimeSpan值,该值包含我们需要的格式的数据。
  • 首先从DateTime获取TimeOfDay结构值。现在把它存储在变量中。由于这个语句中没有提到TimeSpan类型,所以我将变量的类型显式化。然后调整我们用来旋转手臂的属性
Is single precision enough?

  • 对于大多数游戏来说,是的。当处理非常大的距离或规模差异时,这就成了一个问题。然后你将不得不应用一些技巧,比如传送或者相对于摄像机的渲染来保持活跃区域在世界原点附近。虽然使用双精度可以解决这个问题,但它也会使相关数字的内存大小加倍,从而导致其他性能问题。游戏引擎通常使用单精度浮点值,GPU也是如此
  • 我们可以通过显式地从double转换为float来解决这个问题。这个过程称为强制转换,是通过在要转换的值前面的圆括号中写入新类型来完成的。
hoursPivot.localRotation =
        Quaternion.Euler(0f, 0f, hoursToDegrees * (float)time.TotalHours);
                minutesPivot.localRotation =
        Quaternion.Euler(0f, 0f, minutesToDegrees * (float)time.TotalMinutes);
                secondsPivot.localRotation =
        Quaternion.Euler(0f, 0f, secondsToDegrees * (float)time.TotalSeconds);


https://www.zhihu.com/video/1577616643064389632

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-15 20:00 , Processed in 0.169873 second(s), 33 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表