零基础也能学会的DIY 转动的时钟——Unity Shader基础之Game Object 和 Script (一)

1,281 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

本文参考 unity shader 教程,有能力的同学可直接阅读原版教程,也可以参考本教程一起理解,原创不易,先赞后看。感谢大家支持,欢迎评论区批评探讨~

由于前面讲过一节: 快速入门 Unity ,所以有些很小的细节将不详细提及,请大家自行补充阅读。

基础系列将包括如下内容:

  • 游戏对象(Game Object) 和 脚本(Script)
  • Building a Graph
  • 数学曲线(MathematicalSurfaces)
  • 性能测量(Measuring Perforance)
  • Compute Shaders
  • Jobs
  • Organic Variety

一、创建一个工程

1.1 新建一个工程

1.2 编辑器的布局

  • 默认的编辑器的布局

    image.png

1.3 Packages

  • 通过 Window --> Package Manager 菜单项访问

    image.png

  • 通过移除操作,最终留下 Custom NUnitTest FrameworkVisual Studio Editor

    image.png

  • 最终留下

    image.png

  • 通过 Edit --> Project Settings,选择他的 Package Manager 分类 然后选中 Advanced Settings下面的 Show Dependencies .

    image.png

    image.png

1.4 Color Space

  • Player-->Other Settings panel-->Rendering-->Color Space-->Linear

    image.png

1.5 Sample Scene

  • 总览

    image.png

  • 工程窗口默认的使用了 a two-column layout,你可以通过下图中的三个点的按钮选择 a one-column 的布局。

    image.png

  • 对比如下

    image.png image.png

  • sample scene 包含了一个 main camera 和 a directional light.

graph LR
a["sample scene"] --> b["main camera"]
a--> c[" directional light"]

二、构建一个简单的时钟

2.1 创建一个 Game Object

  • 我们需要一个 game object 去代表时钟

  • 右键 SampleScene --> GameObject --> Create Empty

    image.png

  • 把它重命名为: Clock

    image.png

  • 可见一个操纵工具在 game object 的位置,它在世界的中心

    image.png

  • 下面是这些工具的快捷键

    image.png

2.2 创建一个时钟面

  • 向场景中添加圆柱体(Cylinder)

  • 右键 SampleScene --> GameObject --> 3D Object --> Cylinder

    image.png

  • 确保和我们的时钟它有相同的 Transform

  • 可以看到它有: MeshFilter

    image.png

  • 有:MeshRenderer,它决定了:

    • 被渲染的材质是什么
    • 默认材质是什么

    image.png

  • 还有:CapsuleCollider,它是3D物理。该对象表示一个圆柱体,但它有一个胶囊对撞机,因为Unity没有原始的圆柱体对撞机。我们不需要它,所以我们可以去掉这个组件。

  • 压扁它,数据如下图

    image.png

  • 在 Unity 中 X 轴指向右(right), Y 轴指向上(up), Z 轴指向前(forward).

2.3 创建时间标记

  • 通过右键 --> GameObject --> 3D Object --> Cube, 命名为 Hour Indicator 12

    image.png

  • 移除它的 BoxCollider 组件

  • 创建材质

    image.png

  • 修改它的名字为 Hour Indicator

    image.png

  • 修改它的材质:颜色

    image.png

  • 修改后效果如下

    image.png

  • 右键 Hour Indicator 12 --> Edit --> Duplicate ,复制当前的 Object

    image.png

  • 修改它的名字 Hour Indicator 6

    image.png

  • 类似的你可以尝试画出其他时间标记

    image.png

    image.png

    image.png

2.4 创建时钟指针

  • 同样的方式,比如我们复制 12 点的标记

    image.png

  • 然后命名为:Hours Arm,并对其参数进行修改,如下所示:

    image.png

  • 同样的使他保持与时间标记同样的材质,拖拽即可完成

    image.png

  • 指针(Arm)必须绕着时钟的中心旋转,但是改变它的Z轴旋转会使它绕着自己的中心旋转。这是因为旋转是与游戏对象的本地位置有关。

  • 为了创建适当的旋转,我们必须引入一个枢轴(pivot)对象并旋转它。因此,创建一个新的空游戏对象并将其作为Clock的子对象。您可以通过层次结构窗口中的Clock上下文菜单直接创建对象。命名它为 Hours Arm Pivot,并确保它的 posion 和 rotation 为零,并且它的 scale 都为1。然后让 Hours Arm 成为 pivot 的子节点,如下所示。

    image.png

  • 同理构造 Minutes Arm Pivot 和 Seconds Arm Pivot

    image.png

  • Minutes ArmTransform 

    image.png

  • Seconds ArmTransform 

    image.png

  • 用同样的方法,为他们创建材质(可以根据你的喜好)。

三、让时钟动起来

通过 C# 脚本创建一个自定义组件类型

3.1 C# 脚本资产(Script Asset)

  • 在下面选中 Assets 按照图上方式创建 C# 脚本

    image.png

  • 命名为 Clock

  • 为了整洁,创建文件夹,并把脚本收进去;材质也归类进 Materials,如下所示。

    image.png

  • 双击打开

    • 如果这里出现一些问题,可以参看上节教程的第六章。

3.2 定义组件类型

  • 修改代码,将原有的代码修改为:

    public class Clock {}
    
  • 我们的自定义组件必须是 Unity 的 MonoBehaviour 类型的扩展, 需要继承它的数据和函数。

  • 继续修改代码

    public class Clock : MonoBehaviour {}
    
  • MonoBehaviour 属于命名空间 UnityEngine,继续修改代码

    using UnityEngine;
    
    public class Clock : MonoBehaviour {}
    
  • 为了为钟面所用,我们需要将脚本拖拽到桌面上

    image.png

3.3 Getting Hold of an Arm

  • 指针通过调节它的 Transform 组件来实现旋转。

  • 一旦这个字段序是可序列化的,Unity 将检测到这个并且在这个有我们定义的时钟游戏对象的时钟组件的监视窗口上展示它。如果你听的一知半解,没关系,往下看就是了。

  • 我们必须将 Arm pivot 的 Transform 组件添加到时钟中。

  • 这可以通过在其代码块中添加一个数据字段来实现,该数据字段定义为一个名称后跟一个分号。

    Transform hoursPivot;
    
  • 我们可以通过将字段声明为可序列化来改变这一点。这意味着当 Unity 保存场景时,它应该包含在场景的数据中,这是通过将所有数据放入一个序列——序列化——并将其写入一个文件来实现的。

  • 字段默认是私有的,这就意味着它仅仅能够被属于 Clock 的代码访问。但是这个类并不知道我们的 Unity 场景(scene),所以没有直接的方法用正确的对象关联这个字段(比如这里的:hoursPivot)。我们可以改变它通过把这个字段声明为可序列化的(serializable)。这意味着当 Unity 保存场景的时候,这个字段(这里是:hoursPivot)被包含在场景数据中,也就是说把所有的数据放到了一个序列中,实现了序列化它,并且把它写入了一个文件。

    [SerializeField]
    Transform hoursPivot;
    
  • 一旦该字段是可序列化的,Unity 将检测到它,并将其显示在 Clock 游戏对象的 Clock 组件的检查器窗口中,如下所示。

    image.png

  • 要进行正确的连接,将 Hours Arm Pivot 从层次结构中拖到 Hours Pivot字段上,或者,使用字段右侧的圆形按钮,并在弹出的列表中搜索选择。

    image.png

  • 选中后结果如下

    image.png

3.3.1 让三个 Arms 都有响应

  • 同样的方式设置minutesPivot、secondsPivot

    [SerializeField]
    Transform hoursPivot;
    
    [SerializeField]
    Transform minutesPivot;
    
    [SerializeField]
    Transform secondsPivot;
    
  • 在编辑器中关联其他两个Arms

    image.png

3.4 唤醒(Waking Up)

在clock 中使用 Awake 函数。

void Awake () {}

3.5 通过代码旋转

  • 四元数(quaternion) 四元数基于复数,用于表示3D旋转。尽管它们比单独的X、Y和Z旋转角度的组合更难理解,但它们有一些有用的特性。例如,它们不受万向锁( gimbal lock )的影响。

  • 为了应用这个旋转到 hour arm 上,可以按照下面的方式设置,将时针顺时针旋转30度。

    hoursPivot.localRotation = Quaternion.Euler(0, 0, -30);
    
  • 各种旋转的差异

    localRotationrotation
    旋转相对它的父节点在世界空间最终的旋转
  • 设置好代码之后,按照下图方式进行启动,将看到下面的状态。

    1oclock.gif

3.6 获取当前时间

  • 增加下面的代码

    Debug.Log(DateTime.Now);
    
  • 总体代码如下所示

    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);
            }
    }
    

    image.png

  • 大家可以自己尝试,将时钟的时间改成当前的时间

    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);
    }
    
  • 简化一下重复的代码

    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);
    }
    
  • 此时的时钟将显示你调用时刻的时间,但仍然不能动起来

  • 那么如何让时钟动起来呢?

3.8 让时钟动起来

  • 将 Awake 改成 Update ;因为 Update 函数每一帧都在刷新,所以对于想要显示每一帧的变化的一些操作,都可以写在 Update 中
    using System;
    using UnityEngine;
    
    public class Clock : MonoBehaviour
    {
        const float hoursToDegrees = -30f;
        const float minutesToDegrees = -6f;
        const float secondsToDegrees = -1f;
    
    
        [SerializeField] 
        Transform hoursPivot, minutesPivot, secondsPivot;
    
        //void Awake () //调用时候的一帧,rendering the scene once
        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);
    
        }
    }
    
  • 此时点击 play 按钮,你将看到下面的变化,始终动起来了!

    mechanicalClock.gif

  • 目前实现的时钟具有机械感,那么如何使秒针连续的转动呢?

3.9 让时钟连续转动

DateTime TimeSpan
不包含分数数据有一个 TimeOfDay 属性,可以表示分数

TimeSpan 属性提供了双精度浮点型的数值。

  • Unity 的代码仅仅对单精度浮点型数据有效。

  • 游戏引擎一般使用单精度浮点型数值,GPU 也是如此。

  • 所以这里将 DateTime 修改成了 TimeSpan,如下所示:

    image.png

  • 修改后的总体代码如下所示

    using System;
    using UnityEngine;
    
    public class Clock : MonoBehaviour
    {
        const float hoursToDegrees = -30f;
        const float minutesToDegrees = -6f;
        const float secondsToDegrees = -1f;
    
    
        [SerializeField] 
        Transform hoursPivot, minutesPivot, secondsPivot;
    
        //void Awake () //调用时候的一帧,rendering the scene once
        void Update()//调用时候的每一帧都在刷新
        {
            TimeSpan time = DateTime.Now.TimeOfDay;
            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);
    
        }
    }
    
  • 最后你将看到下面的动画效果,一个连续转动的时钟就完成了

    ContinuouslyClock.gif