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

[简易教程] [U1001]UnityC#教程——时钟

[复制链接]
发表于 2020-11-25 09:07 | 显示全部楼层 |阅读模式
原文链接:Clock, a Unity C# Tutorial

时钟——一个简易的时间显示装备

    在层级面板中创建一个物体编写一个脚本并挂载到物体上使用命名空间通过方法函数更新物体根据时间旋转物体

在本教程中我们将编写一个C#脚本来旋转一个简易时钟的表针们。

您需要对Unity编辑器有最基础的认识与理解,如果您已经花了几分钟来掌握它的话那么本文非常适合您阅读

您将很快做出来像这样的时钟
1.创建时钟

在我们的工程中不会使用任何额外的资源包。 默认场景中包含一个位于(0, 1, -10) 并看向Z轴的摄像机(Camera)。通过选择该Camera并在菜单中选择 GameObject / Align View 以在场景视角中获取该Camera的视角。
我们需要一个物体结构来表示时钟。在菜单中选择 GameObject / Create Empty 以创建一个空的游戏物体(GameObject),将其坐标设置为 (0, 0, 0),并将其命名为 Clock。在Clock下创建三个空的子物体(child object),分别命名为HoursMinutes,和Seconds,并确保它们的坐标均为(0, 0, 0)
    什么是游戏物体(GameObject)?
      简单地说,所有被置于场景中的物体都是一个GameObject。它包含名字(name)、标签(tag)、层(layer)和一个Transform组件,并且可以被设置成为静态(static)物体。它本身只是个容器,不会做任何的事情。您可以用在GameObject上挂载组件(components)并将其他物体放入该物体的方式将其变成您需要的物体。
    什么是子物体(child object)?
      如果您将一个物体放入其他物体(通过在层级面板中拖拽的方式)那么这个物体就会被认定是另一个物体的子物体。父物体的Transform组件的数据会被子物体继承并且优先应用。所以当子物体的位置(position)被设置为(10, 0, 0)且父物体的位置是(2, 1, 0)时,这个子物体的最终位置是(12, 1, 0)。但如果父物体的角度(rotation)被设置为了(0, 0, 90),那么子物体会围绕着父物体旋转(0, 0, 90)且最终位置会变为(2, 11, 0)。比例(scale)也以相同的方式被继承。

我们将会用简单的盒状物来代替时钟的表针。在菜单中选择 GameObject / Create Other / Cube 分别给每一根表针创建一个子方块。将Hours方块设置position为(0, 1, 0),scale为(0.5, 2, 0.5);将Minutes方块设置position为(0, 1.5, 0),scale为(0.25, 3, 0.25);将Seconds方块设置position为(0, 2, 0),scale为(0.1, 4, 0.1)

时钟的组建与层级
unitypackage
2.启动时钟

我们需要一个脚本来驱动时钟。在工程面板中通过 Create / C# Script 创建一个C#脚本并命名为 ClockAnimator 。打开该脚本并清空它的其他内容以方便我们重新开始。首先我们需要需要使用来自 UnityEngine 命名空间的内容,其次我们需要声明 ClockAnimator 这个类,我们将其设置为公有类并且继承自 MonoBehavior 。内容如下——
  1. using UnityEngine;
  2. public class ClockAnimator : MonoBehaviour
  3. {
  4. }
复制代码
    什么是命名空间(namespace)?
      命名空间看上去像是某个网站域名,但实际上这是代码相关的内容。举个例子:MonoBehaviour 是包含在 UnityEngine 中的默认命名空间,所以可以被 UnityEngine 编辑处理。就像域名一样,命名空间可以被嵌套。命名空间和域名最大的区别是两者的书写方式正好相反,比如我们会用 com.unity3d.forum 来代替 http://forum.unity3d.com。举个例子:ArrayList 类型存在于 Collections 命名空间中,而 Collections 又存在于 System 命名空间中,因此需要引用 System.Collections.ArrayList 来获取。在声明了需要使用的命名空间后,当我们再次引用源自该命名空间的其他内容时就不必再重复引用。所以,当我们引用了 UnityEngine 后,我们可以直接引用 MonoBehaviour 来代替 UnityEngine.MonoBehaviour 。
    什么是类(class)?
      类是用来在内存中创造物体的,并定义这些物体上包含了什么数据以及物体行为的蓝图。
    什么是MonoBehaviour?
      MonoBehaviour是存在于 UnityEngine 命名空间中的类。如果您想要创建一个可被当作Unity组件的类,那么它需要继承自MonoBehaviour。MonoBehaviour由很多有用的内容构成并且可以让物体不断更新。


该脚本为我们提供了一个最干净清爽的类,让我们可以用来创建组件。在保存之后,您可以用从项目面板拖拽至层级面板的方式,也可以按下在物体上的 Add Component 按钮的方式将它挂载到 Clock 物体上。

被挂载到Clock上的ClockAnimator
为了让表针动起来,我们需要操控它们的Transform组件。在脚本中为每一个表针添加一个公有的Transform变量并保存。这些公有变量将会变成组件的参数并供你在编辑器中关联物体。编辑器将会自动获取被关联物体的Transform组件。选择 Clock 物体,将对应的物体拖拽到对应的参数中。
    什么是变量(variable)?
      变量是可以被改变的值。变量可以是一个对象的引用(即引用类型),也可以是类似于整数的值(即值类型)。一个变量在声明其名字之前必须要定义它的类。变量只存在于被定义的范围内。默认情况下,如果一个变量包含于某个类,那么每个包含这个类的对象均会声明一个属于它自己的该变量。但当变量被设置为静态时,在该类中将仅存在一个该变量,同时无视对象的数量。如果变量是在方法中被定义,那么可以认为只有当该方法被调用时该变量才存在。

  1. using UnityEngine;
  2. public class ClockAnimator : MonoBehaviour
  3. {
  4.         public Transform hours, minutes, seconds;
  5. }
复制代码


空参数和关联过物体的ClockAnimator
接下来,我们将要给脚本添加一个Update方法。这是一个特殊的方法,会在每一帧被Unity调用一次。我们用它来设置时钟表针的角度。
  1. using UnityEngine;
  2. public class ClockAnimator : MonoBehaviour
  3. {
  4.         public Transform hours, minutes, seconds;
  5.         private void Update ()
  6.         {
  7.                 // currently do nothing
  8.         }
  9. }
复制代码
什么是方法(method)?

      方法就是一段在中被定义的行为。它可以接受一个输入值并返回一个输出值。输入值可以在方法名之后被定义且作为参数被提供,或者也有可能没有输入值。方法的类型即为它的输出值,如果无需提供输出值则该方法将被定义为void默认情况下,方法定义了对象的行为,但方法也可以定义一些不需要对象的行为,例如被标记为静态(static)的方法。

Update方法应当被设置为公有吗?

      Update方法和一些其他的方法是Unity的特殊方法。Unity将会在适当的时候找到并调用它们,与我们如何声明它们无关。我们不建议(译注:原文此处用的shouldn't)将Update方法声明为公有方法,因为没必要被除Unity引擎之外的其他对象调用。在此处的编程思想是把一切不会在类外部被调用的方法都设置为私有。私有成员将不会受到来自外界的干扰,也不会与外界建立依赖关系,这将减少可能发生的BUG。我们可以省略私有声明,因为方法与字段在默认的时候都是私有的。我(译注:作者)更倾向于将它们标明,这样可以更明显的起到提醒作用。


在保存脚本后,编辑器将会注意到我们写了一个Update方法并且将会在组件前显示一个检查框(checkbox)以方便我们决定该组件是否被启用,当然在这里我们要保持其启用。

被checkbox装饰后的ClockAnimator
时针将每小时旋转360/12度,分针将每分钟旋转360/60度,秒针将每秒钟旋转360/60度。接下来让我们用私有常量浮点数来定义这些值。
  1. using UnityEngine;
  2. public class ClockAnimator : MonoBehaviour
  3. {
  4.         private const float
  5.                 hoursToDegrees = 360f / 12f,
  6.                 minutesToDegrees = 360f / 60f,
  7.                 secondsToDegrees = 360f / 60f;
  8.         public Transform hours, minutes, seconds;
  9.         private void Update ()
  10.         {
  11.                 // currently do nothing
  12.         }
  13. }
复制代码
    关于常量(const)有什么特殊之处?
      关键字const表示了一个恒定不变的常量。它的值会在编译期间被计算并确定且无论何时一旦被引用则立刻获取其值。顺带一提,编辑器将会预编译任何常数表达式,因此写(1 + 1)和直接写 2 都会得到相同的结果。


在每次调用Update的时候我们都需要知道当前的确切时间以让时钟正常工作。在 System 命名空间下有一个名叫 DateTime 结构体,可以用来处理这方面的事情。在 DateTime 中有一个叫作 Now 的静态参数,它包含了当前的时间。在每次调用Update的时候我们都需要获取该参数的值并且将它保存在一个临时变量中。
  1. using UnityEngine;
  2. using System;
  3. public class ClockAnimator : MonoBehaviour
  4. {
  5.         private const float
  6.                 hoursToDegrees = 360f / 12f,
  7.                 minutesToDegrees = 360f / 60f,
  8.                 secondsToDegrees = 360f / 60f;
  9.         public Transform hours, minutes, seconds;
  10.         private void Update ()
  11.         {
  12.                 DateTime time = DateTime.Now;
  13.         }
  14. }
复制代码
    什么是结构体(struct)?
      结构体是一种与类相似的蓝图。而二者之间的差异是结构体本身是像是整数一样的值类型,而不是像对象一样的引用类型。
    什么是参数(property)?
      参数是一种被伪装成变量的方法。它也有可能是只读的或是只写的。需要注意的是被Unity编辑器显示出来的变量通常也会被叫做参数,但和此处所指的参数是不同的。


为了让表针旋转,我们需要用四元数(Quaternion)来直接改变表针的 localRotation 。在 Quaternion 中包含了一个非常棒的方法可以让我们定义一个任意的旋转。
因为我们正看向Z轴并且Unity采用了左手坐标系,所以表针需要绕着Z轴负方向旋转。
  1. using UnityEngine;
  2. using System;
  3. public class ClockAnimator : MonoBehaviour
  4. {
  5.         private const float
  6.                 hoursToDegrees = 360f / 12f,
  7.                 minutesToDegrees = 360f / 60f,
  8.                 secondsToDegrees = 360f / 60f;
  9.         public Transform hours, minutes, seconds;
  10.         private void Update ()
  11.         {
  12.                 DateTime time = DateTime.Now;
  13.                 hours.localRotation =
  14.                         Quaternion.Euler(0f, 0f, time.Hour * -hoursToDegrees);
  15.                 minutes.localRotation =
  16.                         Quaternion.Euler(0f, 0f, time.Minute * -minutesToDegrees);
  17.                 seconds.localRotation =
  18.                         Quaternion.Euler(0f, 0f, time.Second * -secondsToDegrees);
  19.         }
  20. }
复制代码
现在的时间是12:44
    什么是四元数(quaternion)?
      四元数基于复数,通常用来代表3D旋转。比起简单的三维向量来说,四元数更难理解,但也比三维向量更好用。在 UnityEngine 命名空间下包含了名为 Quaternion 的结构体
    为什么不用rotation?
      localRotation 指的是Transform组件的真实旋转,它不依赖于其父物体的旋转。如果我们想要旋转一个时钟的表针,那么它的表针与时钟本身的旋转应当是相互独立的。rotation 指的是能观察到的Transform组件的最终旋转,它包含了其父物体的旋转。如果我们用 rotation 旋转的话那么当我们旋转时钟的时候表针的旋转就会因为rotation的补偿而导致与时钟本身不匹配。

unitypackage
3.改善时钟

时钟成功运行了!在运行模式下,我们的时钟显示出了当前的时间。但是,这个时钟看上去像是电子钟一样只会阶段性变化。让我们添加一些设置来让时钟看起来更像一个真正的时钟。为脚本添加一个公有布尔值 analog 以方便我们决定在Update方法中需要做什么事情。我们可以在编辑器中和运行模式下设置这个值的开与关。
  1. using UnityEngine;
  2. using System;
  3. public class ClockAnimator : MonoBehaviour
  4. {
  5.         private const float
  6.                 hoursToDegrees = 360f / 12f,
  7.                 minutesToDegrees = 360f / 60f,
  8.                 secondsToDegrees = 360f / 60f;
  9.         public Transform hours, minutes, seconds;
  10.         public bool analog;
  11.         private void Update ()
  12.         {
  13.                 if (analog)
  14.                 {
  15.                         // currently do nothing
  16.                 }
  17.                 else
  18.                 {
  19.                         DateTime time = DateTime.Now;
  20.                         hours.localRotation =
  21.                                 Quaternion.Euler(0f, 0f, time.Hour * -hoursToDegrees);
  22.                         minutes.localRotation =
  23.                                 Quaternion.Euler(0f, 0f, time.Minute * -minutesToDegrees);
  24.                         seconds.localRotation =
  25.                                 Quaternion.Euler(0f, 0f, time.Second * -secondsToDegrees);
  26.                 }
  27.         }
  28. }
复制代码
添加了analog后的ClockAnimator
在不同的 analog 设置下我们应当采用略有不同的方法。我们用 TimeSpan 类型的 DateTime.Now.TimeOfDay 来代替 DateTime.Now 。它可以将已经过去的时、分、秒转化为分数。因为这些值是作为double提供的,因此我们需要将它们强制转换(译注:原文为cast)为float
    什么是强制转换?
      强制转换改变了值的类型。您需要将类型写入值前的括号内。对值的强制转换将会舍弃掉值的一部分内容,例如将浮点数转换为整数时,小数部分就被舍弃了。将对象引用进行强制转换为其它类型时,对象本身不会改变,会改变的只有处理后的引用。您只应当在十分清楚对象类型的情况下才可以这样做。

  1. using UnityEngine;
  2. using System;
  3. public class ClockAnimator : MonoBehaviour
  4. {
  5.         private const float
  6.                 hoursToDegrees = 360f / 12f,
  7.                 minutesToDegrees = 360f / 60f,
  8.                 secondsToDegrees = 360f / 60f;
  9.         public Transform hours, minutes, seconds;
  10.         public bool analog;
  11.         private void Update ()
  12.         {
  13.                 if (analog)
  14.                 {
  15.                         TimeSpan timespan = DateTime.Now.TimeOfDay;
  16.                         hours.localRotation = Quaternion.Euler(
  17.                                 0f, 0f, (float)timespan.TotalHours * -hoursToDegrees);
  18.                         minutes.localRotation = Quaternion.Euler(
  19.                                 0f, 0f, (float)timespan.TotalMinutes * -minutesToDegrees);
  20.                         seconds.localRotation = Quaternion.Euler(
  21.                                 0f, 0f, (float)timespan.TotalSeconds * -secondsToDegrees);
  22.                 }
  23.                 else
  24.                 {
  25.                         DateTime time = DateTime.Now;
  26.                         hours.localRotation =
  27.                                 Quaternion.Euler(0f, 0f, time.Hour * -hoursToDegrees);
  28.                         minutes.localRotation =
  29.                                 Quaternion.Euler(0f, 0f, time.Minute * -minutesToDegrees);
  30.                         seconds.localRotation =
  31.                                 Quaternion.Euler(0f, 0f, time.Second * -secondsToDegrees);
  32.                 }
  33.         }
  34. }
复制代码
analog模式下显示12:56的时钟
现在我们的时钟看上去就像是真的一样!

unitypackage

本帖子中包含更多资源

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

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

本版积分规则

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

GMT+8, 2024-6-6 08:31 , Processed in 0.108196 second(s), 28 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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