第 0 章 Unity概要

第 0 章 Unity概要

{%}

0.1 Unity 基础  Concept

为了给第 1 章的实战教程热身,本章主要复习一下 Unity 的基本用法。我们先开发一个简单的小游戏,并通过这个开发实例,来回顾一下 Unity 中游戏开发的步骤。

Unity 功能强大,为防篇幅冗长,本章将重点介绍那些在 Unity 开发中通用的方法与规则。如果各位读者想再复习一下 Unity 的基本用法,或者已经有一阵子没用 Unity 开发过游戏了,不妨亲手实践一下本章的实例教程。

当然,因为本书的主题是小游戏的开发,所以在入门教程中并未涉及太多超出本书主题的内容。如果读者想更深入地了解 Unity 开发的相关内容,请参考 Unity 的官方手册或其他教材。

另外,本章的后半部分还对 Unity 所支持的两门编程语言 C# 和 JavaScript 作了简单比较。这两门语言各有特色,彼此存在许多不同之处。不过如果读者了解其中一门语言,学习另外一门应该不是什么难事。

书中的游戏实例都是用 C# 开发的。建议仅了解 JavaScript 的读者在学习本书的后续部分之前,先浏览本章的内容。

最后,本章还讲解了 Unity 开发中非常重要的“预设”(prefab)概念。

本章的内容都不算复杂。下面就让我们放松心情,开始游戏开发之旅吧。

0.1.1 脚本一览

文件

说明

Player.cs

用于控制小方块的运动

Ball.cs

用于控制小球的运动

Launcher.cs

用于控制发射台和小球的发射

0.1.2 本章小节

  • 入门教程(上)——创建项目

  • 入门教程(下)——让游戏更有趣

  • C# 和 JavaScript 的对比

  • 关于预设

0.1.3 本章开发的小游戏

  • 用小方块把右边飞来的小球弹开

  • 点击左键使小方块起跳

  • 点击右键发射小球

{%}

0.2 入门教程(上)——创建项目 Tips

0.2.1 概要

在这篇教程中,我们将试着制作一款仅仅使用小方块把小球弹飞的简单小游戏。估计有些读者看到这里可能会想:“这根本不叫游戏吧。”

但是,这种爽快的操作感恰恰是动作游戏的趣味所在。说不定我们的这个小游戏,能为读者带来开发新游戏的灵感呢。

如果读者正在学习 Unity,或者正在为游戏创作寻找灵感,那么我们强烈建议你亲手完成这个游戏的制作。只要参照本教程的步骤一步步操作,并不会花费太多的时间。

另外,关于本游戏项目的完整版,请参考随书下载资料中 Chapter0 文件夹下的 Tutorial 文件。

0.2.2 创建新项目

首先,让我们为游戏创建一个 Unity 项目吧。

启动 Unity,在窗口顶部菜单中依次点击 File → New Project,将打开标题为 Unity - Project Wizard 的窗口(图 0.1)。

{%}

图 0.1 创建新项目

在 Project Location 文本框中输入项目文件名。这里我们指定为 Tutorial。

Import the following packages: 下面的复选框项可以都不用选。这里列出的都是一些可用于 Unity 功能扩展的资源包。因为这次我们开发的游戏并不需要使用这些,所以请把所有的复选框都设置为取消状态。在开发的过程中如果发现需要添加某些扩展功能,也可以重新导入。

按下 Create 按钮后,Unity 将重新启动并显示我们创建好的空项目。

接下来,点击 Scene 标签页切换到场景视图。如果画面中没有显示网格线,请点击叠加图标。场景视图上方画着山峰的图标就是叠加图标(图 0.2)。

{%}

图 0.2 显示网格线

0.2.3 创建地面(创建游戏对象)

我们先创建一个地面对象。在窗口顶部菜单中依次点击 GameObject → CreateOther → Plane,场景视图中央将出现一个平板状的游戏对象。同时层级视图中也增加了一项 Plane(图 0.3)。这就是本次游戏中被用作地面的游戏对象。

{%}

图 0.3 创建 Plane 游戏对象

因摄像机所处位置的不同,读者看到的画面可能会和图中不一致,请不用在意,后面将进行调整。

0.2.4 创建场景,保存项目

下面把目前的工作成果保存一下。

观察 Unity 的标题栏,能发现在 Unity-Untitled-Tutorial-PC and Mac Standalone 这一文本右侧有一个“*”符号(图 0.4)。这个“*”符号表示当前项目文件需要保存。保存后该符号就会消失,之后如果又做了什么操作需要重新保存,该符号会再次出现。

{%}

图 0.4 “*”表示文件需要保存

其实不仅是 Untiy,我们在使用任何新工具时,一开始都最好了解一下文件的保存方法。如果在未保存数据的情况下关闭了 Unity,那么所有的工作内容都将丢失。虽然在关闭时会弹出“文件未保存”的提示对话框,但是在不经意间将文件强行关闭的情况也经常发生。而且,在出现“操作出现错误,无法撤销”的情况时,如果事先保存过文件,就可以很方便地恢复到以前的状态。

在 Unity 中,为了保存项目,必须创建场景。所谓场景,就是例如“主题画面”“游戏中”“排行榜”这样的被用来划分游戏状态的部分(图 0.5)。在这个游戏中,我们只需要创建 1 个场景。

{%}

图 0.5 项目中的“场景”

在窗口顶部菜单中依次点击 File → Save Scene(图 0.6)。

{%}

图 0.6 保存场景

这时会弹出文件保存对话框。在文件名处填入场景的名称,这里我们输入 GameScene。

可以看到标题栏上的“*”符号消失了。这意味着所有的工作成果都已经被保存。关闭 Unity,再次打开该项目,这时应该和现在的状态是一样的。那么我们就可以放心地进行下一步了。到目前为止,项目视图中也增加了 GameScene 项(图 0.7)。

{%}

图 0.7 成功创建场景

0.2.5 让地面围绕原点移动

通过 GameObject 菜单创建的游戏对象会显示在场景视图的中央区域。为了方便后面的开发工作,我们先把地面对象和场景摄像机移动到原点附近。

选中层级视图中的 Plane 项。检视面板中将出现 Transform 标签页。请把 Position 下的“X”“Y”“Z”全部设置为 0(图 0.8)。地面对象将移动到原点。因为摄像机位置的关系,屏幕上可能无法看见地面对象,稍后我们将把摄像机也移动到原点附近,请读者不用担心。

{%}

图 0.8 将 Plane 移动到原点

如果在检视面板的 Transform 下看不到 Position 的相关内容,请点击 Transform 旁边的三角形符号。展开标签页后,将显示 Transform 组件的相关参数(图 0.9)。

{%}

图 0.9 为显示 Position 而进行的操作

改变 Position 的值以后,再次通过层级视图选中地面对象,并试着按下 F 键,这时场景视图中的地面对象将获得焦点并显示在画面中央(图 0.10)。

{%}

图 0.10 将摄像机移动到能够看见地面的位置

按下 F 键使画面的视角移动到被选中对象的正面,这一功能非常方便,请读者务必掌握。

0.2.6 调整场景视图的摄像机

我们稍微调整一下摄像机的角度,使之能够从正面视角俯瞰地面。

首先,按住 Alt 键的同时拖动鼠标左键,摄像机将以地面为中心旋转。而如果按住 Alt 键和 Ctrl 键(苹果 Mac 环境则为 Command 键)的同时拖动鼠标左键,摄像机则将平行移动。滚动鼠标滚轮,画面将向着场景深处前后移动(图 0.11)。

{%}

图 0.11 场景视图的摄像机操作

请读者参考场景视图右上方的 3D 图标,并旋转摄像机,使 X 向右,Y 向上,Z 向内。

如果平行移动摄像机后导致地面离开了视野,请再次选中地面对象然后按下 F 键。大约调整为类似于图 0.12 的画面效果后就可以继续往下了。

{%}

图 0.12 将摄像机移动到正对地面处

0.2.7 创建方块和小球(创建游戏对象并调整坐标)

创建完地面后,接下来我们将创建代表玩家角色的小方块。在窗口顶部菜单中依次点击 GameObject → CreateOther → Cube,一个叫作 Cube 的游戏对象将被创建在场景视图的中央(图 0.13)。

{%}

图 0.13 创建小方块对象

默认的初始位置会让它看起来像陷在了地面中,我们可以用移动工具来调整它的位置。

所谓移动工具,指的是和游戏对象重叠显示的用红、绿、蓝三种箭头组合而成的 Unity 编辑器的 UI。红、绿、蓝的箭头分别代表 X 轴、Y 轴、Z 轴。RGB=XYZ,很好记。因为现在我们想往上移动方块,所以就拖动绿色的箭头。可以看到小方块会随着鼠标光标往上移动(图 0.14)。

{%}

图 0.14 移动游戏对象

如果小方块上没有显示移动工具,就表示小方块处于未选中状态。请在场景视图中点击小方块对象,或者在层级视图中选中该小方块(图 0.15)。

{%}

图 0.15 显示移动工具

除此之外,我们还要创建一个球体游戏对象。在窗口顶部菜单中依次点击 GameObject → CreateOther → Sphere。同样我们也把它移动到适当的位置(图 0.16)。

{%}

图 0.16 创建 Sphere 游戏对象

接下来再稍稍调整一下小方块的位置。并非只有通过场景视图的移动工具才能改变游戏对象的位置,通过检视面板也可以做到。请回忆下之前我们将地面移动到原点的过程。

首先,在层级视图中选中小方块。把检视面板中 Transform 标签下的 Position 的 X 值由 0 改为-2。可以看到小方块向左移动了一些(图 0.17)。

{%}

图 0.17 移动小方块

如果是粗略地移动,可以通过在场景视图中拖动对象来完成,而细微的调整则通过检视面板操作比较好。虽然位置坐标和旋转角度这些数值并不要求必须是整数,但适当地舍掉小数部分将会为后面的处理带来方便。

0.2.8 运行游戏

再次保存我们的项目文件。在窗口顶部菜单中依次点击 File → Save Scene,覆盖保存文件。以后也都应该像这样在恰当的时候进行一次保存以防止数据丢失。

完成保存后,让我们把游戏运行起来吧。首先,请确认游戏视图标签页右上方的 Maximize on Play 按钮处于按下状态。然后点击画面上方的播放按钮。播放按钮是指位于工具栏中间的播放控件中最左边的三角形按钮(图 0.18)。

{%}

图 0.18 执行游戏

启动游戏后,将自动切换到游戏视图。场景视图中配置好的 3 个游戏对象将以蓝色背景显示出来。如果游戏运行时没有全屏显示,请点击 Maximize on Play 图标。

若希望终止游戏运行,再次点击播放按钮即可。

0.2.9 模拟物理运动(添加 Rigidbody 组件)

下面我们将陆续添加游戏的核心部分。首先是让小方块跳起来。为了实现这个效果,需要为游戏对象添加物理运动组件。

在层级视图选中 Cube,并在窗口顶部菜单中依次点击 Component → Physics → Rigidbody。这样 Rigidbody 组件就被添加到了小方块中,可以在检视面板中看到一项 Rigidbody(图 0.19)。以后我们也都可以像这样在检视面板中确认那些被添加到游戏对象中的各个组件。

{%} 图 0.19 添加 Rigidbody 组件

再次运行游戏看看效果如何。现在小方块将快速落下并在撞到地面时停止(图 0.20)。

{%}

图 0.20 小方块落下

刚才添加的 Rigidbody 组件主要用于赋予游戏对象物理属性以模拟真实的物理运动。游戏对象在物理运动过程中的速度和受力,必须由开发者通过编程来控制。不过在很多游戏中都被广泛使用的重力属性是个例外,Rigidbody 组件默认提供这方面的支持。

0.2.10 让小方块跳起来(添加游戏脚本)

下面我们将添加脚本使小方块能够跳起来。从项目视图的 Create 菜单中选择 C# Script,将生成一个叫作 NewBehaviourScript 的脚本文件。将其名字改为 Player(图 0.21)。

{%}

图 0.21 添加 Player 脚本

目前创建的是一个尚不具备任何功能的空脚本。为了使其能够在游戏中发挥作用,我们还需要按照游戏逻辑来编辑它。

选中 Player 脚本,点击检视面板上的 Open 按钮。这时名为 Mono Develop 的编辑器将会启动,Player.cs 脚本被打开(图 0.22)。

{%}

图 0.22 启动 Mono Develop 编辑器

在项目视图中双击脚本项也能够启动 Mono Develop 编辑器。读者可以根据自己的操作习惯选择其中一种方法。

Mono Develop 启动以后,让我们按照下列代码编辑 Player 脚本。新增一个 jump_speed 数据成员,并重写 Update 方法。Start 方法可以暂不做修改,囿于篇幅,书中略去相关代码。

Player 类(摘要)

public class Player : MonoBehaviour {

    protected float jump_speed = 5.0f;--------------起跳时的速度
    void Update ()
    {
        if(Input.GetMouseButtonDown(0)) {-----------点击鼠标左键触发
            this.rigidbody.velocity = Vector3.up * this.jump_speed;--------设定向上速度
        }
    }
}

在 Mono Develop 中编辑完代码后,必须对其加以保存才能使改动生效。保存时只需按下 Mono Develop 工具栏中的软盘图标即可(图 0.23)。

{%}

图 0.23 保存脚本文件

该图标右侧还有一个多张软盘重叠的图标,它被用于一次性保存所有修改过的文件。若无特殊原因,一般情况下我们都推荐使用它来保存项目。

和 Unity 编辑器一样,Mono Develop 中如果有文件需要保存,标题栏的文件名后也将显示“*”符号。而且编辑中的文件标签页也同样会显示“*”符号。文件被保存后,“*”符号将消失,同时保存按钮变为不可点击状态。

建议读者养成在运行游戏前通过“保存按钮”来确认所有文件都已被保存的习惯。

修改好代码后,让我们把新创建的类组件添加到 Cube 游戏对象中。请从项目视图中将 Player 脚本拖曳到层级视图中的 Cube 对象上。这样就可以把 Player 脚本添加到小方块对象中,现在检视面板中也应该能看见 Player 标签(图 0.24)。

{%}

图 0.24 将脚本添加到方块

再次启动游戏。按下鼠标左键后,小方块将“嘭”地弹起来(图 0.25)。这是因为刚才添加的脚本被执行了的缘故。

{%}

图 0.25 点击左键后小方块跳起来了

Unity 就是像上述流程这样通过添加和修改游戏脚本来控制游戏对象的各种动作的。

0.2.11 修改游戏对象的名字

像 Cube 和 Sphere 这样,刚创建好的游戏对象都直接使用对象的种类作为名称。下面就让我们把它们改为游戏中的名字。

点击层级视图中的 Cube。当背景变为蓝色后再次点击。名称文本将变为可编辑状态,把 Cube 改为 Player 后按下回车(图 0.26)。同理,请把 Plane 改为 Floor,把 Sphere 改为 Ball(图 0.27)。

{%}

图 0.26 把名称 Cube 改为 Player

{%}

图 0.27 对名称 Sphere,Plane 也做修改

0.2.12 修改游戏对象的颜色(创建材质)

所有的对象都使用相同的颜色难免会使游戏显得太单调,下面我们就来尝试为游戏对象上色。首先创建材质。在项目视图的菜单中依次点击 Create → Material,就可以创建一个叫 New Material 的项。把它的名字改为 Player Material。

如果检视面板中未显示 Player Material,请选中项目视图中的 Player Material。在 Main Color 文本右侧有一个白色的矩形。点击白色矩形,将打开标题为 Color 的色彩选择窗口。

色彩选择窗口内的右侧有调色板,点击其中的红色区域。刚才的白色矩形将立即显示为选中的颜色。选择完颜色后关闭选择窗口。

接下来,在项目视图中将 Play Material 拖曳到层级视图中的 Player 上。这意味着把 Play Material 分配给 Player,如此一来,场景视图中的游戏对象 Player 就变成红色了(图 0.28)。

{%}

图 0.28 创建小方块的材质

采用同样的方式创建绿色的 Ball Material 和蓝色的 Floor Material,并分别将它们分配给 Ball 和 Floor 对象(图 0.29)。

{%}

图 0.29 创建小球和地面的材质

0.2.13 让画面更明亮(创建光源)

和场景视图相比,游戏中的画面显得格外昏暗,并且小方块这些游戏对象也没有阴影效果。下面我们将添加灯光,让游戏画面明亮起来。在窗口顶部菜单中依次点击 GameObject → Create Other → Directional Light,添加一个平行光(图 0.30)。

{%}

图 0.30 创建平行光

对游戏空间中的每个角落来说,平行光 Directional Light 照射出的光线都是同一个方向,同一个强度。由于平行光的位置并不影响游戏对象的亮度,所以为了操作方便,我们可以把它放在一个适当的位置。

0.2.14 调整游戏画面的尺寸(调整播放器设置)

最后,我们来调整游戏画面的尺寸(图 0.31)。

{%}

图 0.31 调整游戏画面的尺寸

在窗口顶部菜单中依次点击 File → Build Settings...,打开标题为 Build Settings 的编译设置窗口。

在 Platform 中点击 Web Player,按下 Switch Platform 按钮。PC and Mac Standalone 右侧的 Unity 标记将移动至 Web Player 项。这样设置完以后,编译将生成能够在网页浏览器上运行的文件。不过本篇入门教程不需要生成执行文件,我们将其用于调整游戏画面的尺寸。

点击编译设置窗口中的 Player Settings... 按钮。检视面板中的显示将变为 Player Settings。PerPlatform Settings 下方并排显示着三个按钮。请按下最左边的画有地球标记的按钮。然后分别在 Resolution 的 Default Screen Width 和 Default Screen Height 中填入 640 和 480 并关闭编译设置窗口。

点击游戏标签左下方的 Free Aspect 文本,然后在出现的下拉菜单中选择 Web(640×480)。

再次运行游戏,可以看到游戏画面比之前小了一圈(图 0.32)。考虑到后续的角色配置和 GUI 调整等因素,应该尽量在初期就确定好游戏画面的尺寸。

{%}

图 0.32 游戏画面的尺寸发生变化

0.2.15 小结

按照以往的游戏开发方法,如果不首先编写代码,游戏画面上就不会有任何显示。但是 Unity 却正好相反,允许先配置好小方块和小球这些可见的物体,然后再通过编程来控制它们的动作。从“内在构造(编程)”出发还是从“外观表现(形状)”出发,是 Unity 和传统游戏开发方式的一个很大的区别。

Unity 本身提供了许多常用的标准功能,而在此基础上,开发人员能够通过编写游戏脚本打 造出独特的游戏,这是 Unity 的一大亮点。本教程的后半部分将着重介绍如何使用脚本编程来实现一个游戏特有的玩法。

0.3 入门教程(下)——让游戏更有趣 Tips

0.3.1 概要

我们在教程的前半部分中创建了项目,并且创建了小方块和小球这些游戏对象。还通过添加脚本实现了小方块的弹跳。虽然功能比较简单,但是可以说它完整地表现了使用 Unity 开发游戏的大体流程。

为了让这个游戏变得更加有模有样,下面让我们再进一步完善小方块和小球的动作。

0.3.2 让小球飞起来(物理运动和速度)

目前小球是静止在空中的,下面我们来尝试使它朝小方块飞去。为了令小球能够模拟物理运动,需要添加 Rigidbody 组件。同时我们还要创建一个叫作 Ball 的脚本(图 0.33)。

{%}

图 0.33 将 Rigidbody 组件和脚本添加到小球

忘记相关步骤如何操作的读者请回顾教程(上)中“模拟物理运动”和“让小方块跳起来”这两小节。

添加了 Ball 脚本以后,请对 Start 方法做如下修改。

Ball.Start 方法

void Start()
{
    this.rigidbody.velocity = new Vector3(-8.0f, 8.0f, 0.0f);-----设置向左上方的速度
}

游戏开始后,小球将向画面左侧飞去(图 0.34)。

{%}

图 0.34 小球飞出去了

0.3.3 创建大量小球(预设游戏对象)

目前游戏仅在开始时生成了 1 个小球对象。这当然远远不够,另外也不便于小球速度和方块跳跃高度的调整。接下来我们要做的就是,让游戏中可以随时发射出小球。

为了能够随时创建出小球对象,首先需要对小球对象进行预设

请将层级视图中的 Ball 项拖曳到项目视图中。项目视图中将出现 Ball 项。文本左侧有蓝色方块图标的是预设。同时,层级视图中的 Ball 项文本也会变为蓝色(图 0.35)。

{%}

图 0.35 预设 Ball 对象

请将项目视图中的 Ball 预设拖曳到场景视图中,可以看到场景中会多出一个小球对象(图 0.36)。

{%}

图 0.36 从预设创建游戏对象

预设了游戏对象后,我们就能够非常容易地创建出多个同样的物体。从脚本中创建也很简单。预设是 Unity 中最为重要的概念之一,请读者务必掌握。

在继续下一步之前,我们先删除所有的小球对象。包括最初创建的小球也可一并删除。有了预设后我们就能够随时创建出小球对象,所以请放心删除。

接下来我们要修改预设的名字。修改名字时没有什么特别的规定,不过我们比较推荐采用 Ball Prefab 这类形式的命名,通过添加 Prefab 后缀来标识预设以便理解。

同样,请对小球以外的其他对象也进行预设并重新命名(图 0.37)。

{%}

图 0.37 对所有的游戏对象都进行预设

对于那些在游戏中仅使用一次的游戏对象,并不一定要进行预设。当然如果事先进行了预设,在面临想在测试专用的场景中验证脚本的动作等情况时,将会很方便。

0.3.4 整理项目视图

开发进行到这里,项目视图中已经添加了许多项目。在进行下一步开发之前,让我们先用文件夹将这些项目归类整理。在项目视图左上角的菜单中点击 Create → Folder 后,项目视图中将生成一个文件夹,请把名字改为 Prefab(图 0.38)。

{%}

图 0.38 创建 Prefab 文件夹

然后将预设 Ball Prefab 拖曳到 Prefab 文件夹下(图 0.39)。

{%}

图 0.39 将 Ball Prefab 移动到文件夹下

Ball Prefab 将移动到 Prefab 文件夹下。如果 Ball Prefab 预设未显示,请尝试点击 Prefab 旁的三角形图标。接着把其他预设也移动到 Prefab 文件夹下。

请采用同样的方式创建 Scene、Script、Material 文件夹,并把各项目放到相应的文件夹下(图 0.40)。

{%}

图 0.40 将各项目整理至对应的文件夹下

0.3.5 发射小球(通过脚本创建游戏对象)

接下来,我们将创建用于控制小球发射的游戏对象。在窗口顶部菜单中依次点击 GameObject → Create Empty,并将其名称设为 Launcher Prefab(图 0.41)。因为马上将对其进行预设,所以一开始就在名称后加上 Prefab。

{%}

图 0.41 创建 Empty 游戏对象

和我们之前创建的那些对象有所不同的是,这是一个未添加任何组件的“原始”的游戏对象。如果有小球发射台等模型的话当然更完美,但这里我们没有准备这样的东西,只是创建了一个基本的游戏对象。Empty 意味着“空”,读者可以把它理解为不含任何功能扩展组件的“基本游戏对象”。

请不要忘记对这个游戏对象进行预设。

然后创建 Launcher 脚本,并将其添加到 Launcher Prefab 预设中。之前介绍的方法是将脚本拖曳到层级视图中的游戏对象上,但现在因为对象已经是预设,所以请把脚本拖曳到项目视图中的预设对象上即可(图 0.42)。

{%}

图 0.42 创建 Launcher 脚本

接下来请按照如下代码编辑 Launcher 脚本。除了 Update 方法有变动之外,还增加了 ballPrefab 变量。

Launcher 类(摘要)

public class Launcher : MonoBehaviour {
    public GameObject ballPrefab;------------------------小球预设

    void Update () {
        if(Input.GetMouseButtonDown(1)) {-----------点击鼠标右键后触发
            Instantiate(this.ballPrefab);--------创建 ballPrefab 的实例
        }
    }
}

Instantiate 是通过预设生成游戏对象实例的方法。不过,脚本中并没有对 ballPrefab 变量进行初始化的代码。所以在游戏运行前必须先在检视面板中对 ballPrefab 变量赋予预设对象值(图 0.43)。

{%}

图 0.43 用预设对变量 Ball Prefab 赋值

从项目视图中选择 Launcher Prefab 预设。可以看到在检视面板中的 Launcher(Script) 标签下显示有 Ball Prefab 项。脚本代码中声明的所有 public 成员变量都将在这里列出。往类中新添加的变量默认表示为 None(GameObject),意味着该变量还未被赋值。

请将项目视图中的 Ball Prefab 预设拖曳到这里。

不习惯拖曳操作的读者也可以点击 Ball Prefab 右边的圆圈内带点的图标。Select GameObject 窗口将被打开,这时再选择 Ball Prefab 也可以给变量赋值。

对脚本中的变量进行赋值有很多种方法。其中通过检视面板对预设、材质、纹理等资源变量进行赋值是非常简单的。

那么再次运行游戏看看吧。可以发现每次点击鼠标右键的时候,都会射出一个小球(图 0.44)。

图 0.44 点击右键发射小球

这里,为了和预设对象区分开,我们把脚本中通过 Instantiate 方法生成的游戏对象称为实例,把产生实例的过程称为实例化

0.3.6 删除画面外的小球(通过脚本删除游戏对象)

我们的游戏现在有一个不足:发射出的小球永远不会消失。首先我们来确认跑出游戏画面之外的游戏对象会一直存在这一事实。

关闭游戏视图右上方的 Maximize on Play 按钮,再次启动游戏。这样一来我们在游戏运行时也能查看层级视图和检视面板(图 0.45)。

{%}

图 0.45 在检视面板等可见的同时运行游戏

游戏运行时由脚本动态生成的游戏对象也会被显示在层级视图中。每点击一次鼠标,层级视图中都会增加一个 Ball Prefab(Clone) 游戏对象。可见即使小球已经跑出游戏画面之外,这些游戏对象也并未消失(图 0.46)。

{%}

图 0.46 小球对象未被删除

跑出画面之外的小球不会再回到画面中,所以完全可以删除。请按照下列代码在脚本 Ball.cs 中添加 OnBecameInvisible 方法。该方法可以被添加到 Ball 类定义范围内的任意位置。

Ball.OnBecameInvisible 方法(摘要)

public class Ball : MonoBehaviour {
    (省略其他方法的代码)
    添加:游戏对象跑出画面外时被调用的方法
    void OnBecameInvisible()
    {
        Destroy(this.gameObject);---------------------删除游戏对象
    }
}

OnBecameInvisible 方法是在游戏对象移动到画面之外不再被绘制时被调用的方法。Destroy(this.gameObject) 则是删除游戏对象的方法。请注意传入的参数是 this.gameObject。关于这方面的知识我们在 2.3 节中还会说明。

再次运行游戏,会发现一旦小球跑出画面之外,层级视图中的 Ball Prefab(Clone) 项也就随之消失了(图 0.47)。

{%}

图 0.47 删除画面外的小球

0.3.7 防止小方块在空中起跳(发生碰撞时的处理)

由于点击鼠标左键后小方块就会起跳,因此在目前的程序中,小方块即使在空中也能再次起跳(图 0.48)。但显然我们并不希望它有这样的行为。

{%}

图 0.48 小方块在空中起跳

为了防止小方块在空中再次起跳,我们来添加下列处理。

  • 添加着陆标记

  • 着陆标记值为 false 时不允许起跳

  • 将起跳瞬间的着陆标记设为 false

  • 将着陆瞬间的着陆标记设为 true

请按照下列代码修改 Player 脚本。

Player 类

public class Player : MonoBehaviour {

    protected float jump_speed = 5.0f;
    public bool     is_landing = false;---------------着陆标记

    void Start()
    {
        this.is_landing = false;
    }

    void Update()
    {
        if(this.is_landing) {---------------着陆后触发……
            if(Input.GetMouseButtonDown(0)) {
                this.is_landing = false;-------------------将着陆标记设置为false(未着陆 = 在空中)
                this.rigidbody.velocity = Vector3.up * jump_speed;
            }
        }
    }

    添加: 和其他游戏对象发生碰撞时调用的方法
    void OnCollisionEnter(Collision collision)
    {
        this.is_landing = true;-------------------将着陆标记设置为true(着陆 = 在地面上)
    }
}

当一个游戏对象同其他对象发生碰撞时,OnCollisionEnter 方法将被调用。在该方法中把着陆标记的值设为 true。这样小方块就不能在空中再次起跳了。

0.3.8 禁止小方块旋转(抑制旋转)

在某种程度上完成了小方块和小球的脚本编程后,让我们来调整各相关参数,以使小方块在起跳后能和小球发生碰撞。因为是为了让开发能够继续进行而暂定的数值,所以只要能使小方块和小球发生碰撞即可。这里我们可以采用下列值。

  • 小方块的位置:(-2.0, 1.0, 0.0)

  • 小方块的起跳速度(Player.jump_speed 的值):8.0

  • 小球的位置:(5.0, 2.0, 0.0)

  • 小球的初始速度(Ball.Start 方法中设定的值):(-7.0, 6.0, 0.0)

请从层级视图中选择 Player Prefab,从项目视图中选择 Ball Prefab,来分别在检视面板中设置小方块和小球的位置。小方块的起跳速度和小球的初始速度需通过编辑脚本修改。

现在我们来关注一下小方块和小球发生碰撞后二者的运动情况。碰撞后的运动也被称为反向运动(Reaction)。通过实现各种不同的反向运动效果,就能表现出游戏对象的“重量”、“硬度”以及对碰撞对象的“冲击能量”等特性。

我们注意到,跳跃中的小方块和小球碰撞后会开始旋转(图 0.49)。这虽然符合物理规律,但是并不适用于我们这个游戏。下面就让我们来抑制小方块的旋转。

{%}

图 0.49 碰撞后小方块发生旋转

选择项目视图中的 Player Prefab。打开检视面板中的 Rigidbody 标签可以看到 Constraints 项。点击左边的三角形图标,下面会进一步显示 Freeze Position 和 Freeze Rotation。其中 Freeze Position 用于将游戏对象的位置坐标固定在某些方向上。 Freeze Rotation 则用于固定其角度。

由于我们希望小方块只上下跳跃而不做左右和前后的移动,因此把 Freeze Position 的“X”“Z”前面的复选框选中。 Freeze Rotation 方面则把“X”“Y”“Z”全部选中(图 0.50)。

{%}

图 0.50 抑制小方块旋转

0.3.9 让小方块不被弹开(设置重量)

现在小方块不会再旋转了。但是碰撞后小方块将立即落下,并且小球也不怎么反弹(图 0.51)。

{%}

图 0.51 碰撞后小方块立即下落

如果碰撞后小球能被剧烈地弹开的话,游戏的体验将会更好。下面我们首先来尝试让方块不被小球弹回。通过改变物理计算中用到的“重量”,就能够决定在碰撞发生时两个游戏对象“哪一个把对方弹开”。

选择项目视图中的 Ball Prefab。如之前所述,打开 Rigidbody 标签,将 Mass 项的值由 1 改为 0.01(图 0.52)。Mass 项用于设定游戏对象的重量。两个游戏对象发生碰撞时,Mass 较大的物体将保持原速度继续运动,相反 Mass 值较小的物体则容易因受到冲击而改变移动的方向。

{%}

图 0.52 使方块不被弹开

由于把小球质量变小了,所以碰撞发生后小方块还能够继续上升。与之相反,小球在碰撞后则被往上弹开了。

0.3.10 让小球强烈反弹(设置物理材质)

改变了重量后小方块将不再被小球弹回。但是发生碰撞后的小球仅仅改变了运动的轨道,感觉还不够好(图 0.53)。所以接下来我们就试着让小球能够如橡皮球般剧烈地弹开。

{%}

图 0.53 小球不怎么反弹

首先创建物理材质。从项目视图的 Create 菜单中选择 Physic Material。这时将生成一个名为 New Physic Material 的物理材质。将其名称改为 Ball Physic Material(图 0.54)。

{%}

图 0.54 创建物理材质

相对于用来指定颜色等可视属性的“材质”,“物理材质”用于设定弹性系数和摩擦系数等与物理运动相关的属性。

在项目视图中选择 Ball Physic Material 后,在检视面板中选择 Bounciness,将其值由 0 改为 1(图 0.55)。这个值越大,游戏对象就越容易被“弹开”。

{%}

图 0.55 改变 Bounciness 值

接下来从项目视图中选择 Ball Prefab。把刚才创建的 Ball Physic Material 拖曳到检视面板中 Sphere Collider 标签下的 Material(图 0.56)。

{%}

图 0.56 设置小球的物理材质

不习惯拖曳操作的读者可以点击 Ball Physic Material 右侧的圆形图标。这时 Select Physic Material 窗口将被打开。在这个“物理材质选择窗口”中也可以进行选择设定。

再次运行游戏。与原来相比,现在小球在发生碰撞时嘭地弹开了(图 0.57)。

{%}

图 0.57 碰撞后小球将剧烈弹开

0.3.11 消除“漂浮感”(调整重力大小)

现在游戏中小方块和小球的落地过程就像羽毛掉落一样。简而言之,这是因为游戏中的小方块和小球其实是“庞然大物”。

Unity 在处理数字时,并未特别指定按照米或者厘米为单位进行计算。不过若重力值设为 9.8 的话,就意味着 Rigidbody 组件按照 1.0 = 1 米的尺度模拟物理运动。

目前小方块和小球的尺寸值都是 1.0。也就是说现在的游戏画面是,在直径为 1 米的球体不远处放置着一个边长为 1 米的立方体。可能一直以来在很多读者的想象中,它们的尺寸要远比这个小得多吧。

通过某些手段可以实现大物体缓慢落下的景象。比如在电影的微缩模型摄影中,通过把高速摄影拍下的东西进行慢速播放,就可以把某些小细节放大。有兴趣的读者可以通过网络等搜索相关信息。

减小对象自身的尺寸也可以消除这种慢悠悠落下的感觉,不过这里我们采用另外一种方法,就是增加重力值。增强重力以后,物体落下的速度会变得更快。

在窗口顶部菜单中依次点击 Edit → Project Settings → Physics。检视面板中将切换显示 PhysicsManager。将 Gravity 项的“Y”值稍微提高一些,比如设为 -20(图 0.58)。注意数字前面的负数符号

{%}

图 0.58 消除“漂浮感”

通过增强重力可以减弱物体在运动时的“漂浮感”,不过跳跃的高度和小球的轨道也显得比原来低了。这种情况下可以考虑调整为下列数值。

  • 小方块的起跳速度(Player.jump_speed 的值):12.0

  • 小球的初始速度(Ball.Start 方法中设定的值):(-10.0,9.0,0.0)

0.3.12 调整摄像机的位置

接下来我们调整摄像机的位置。之前摄像机一直处于水平位置进行正面拍摄。现在试着将其抬高一些以便能够向下拍摄。

选择摄像机后,场景视图右下角将出现一个小窗口。这是从摄像机中看到的画面。如果无法看到这个窗口,请在检视面板中展开 Camera 标签(图 0.59)。

{%}

图 0.59 显示摄像机视图

为了能够俯视地面,需要使摄像机在往上偏移的同时绕 X 轴旋转。

调整角度时需把移动工具切换为旋转工具(图 0.60)。请点击工具栏中变换工具的右数第二个按钮。场景视图中叠加在游戏对象上显示的移动工具将变成由 3 个圆组合而成的旋转工具。

{%}

图 0.60 移动工具 和选择工具

如图 0.61 所示在检视面板中输入正确的数值。图 0.62 是输入后的显示效果。当然读者也可以按照自己的喜好填入其他适当的数值。

  • 位置:(0,3,-10)

  • 角度:(6,0,0)

{%}

图 0.61 调整摄像机的位置和角度

{%}

图 0.62 让摄像机俯视地面

0.3.13 修复空中起跳的 bug(区分碰撞对象)

试玩游戏后,我们注意到小方块和小球碰撞后还可以再次起跳(图 0.63)。这是因为我们防止空中跳跃的代码存在些问题。

{%}

图 0.63 防止空中起跳的代码的 bug

之前的编程思路是“碰撞发生即为着陆”。但是,小方块和其他游戏对象的碰撞并非只在着陆的瞬间发生。因为未对小方块的碰撞对象做区分判断,所以和小球碰撞时也被当作“着陆”来处理了。

修改 bug 之前,我们先来验证该 bug 的原因是否真如我们所推测。请回忆前面删除跑出画面外的小球的游戏对象的过程。同样,这里也需要确保检视面板和层级视图在游戏运行时仍然可见。只需把游戏视图标签上的 Maximize on Play 复选框取消就 OK 了。

游戏启动后,在层级视图中选择 Player Prefab。可以在检视面板中的 Player(Script) 标签下看到 Is_landing 项(图 0.64)。这就是在 Player 脚本中定义过的 is_landing 变量。

{%}

图 0.64 在游戏运行时确认脚本中的变量值

游戏刚开始时画面上还没有小球。随着小方块起跳,可以看到 is_landing 复选框由取消变为了选中状态(图 0.65)。

{%}

图 0.65 起跳时 is_landing 变量值的变化

接下来让小球进行碰撞。我们可以看到小方块起跳后变为 false 值的 is_landing 在小方块与小球接触的瞬间又变成 true 值了。

由于运动速度很快不容易确认,我们让游戏逐帧播放。但是要在物体刚起跳后就通过鼠标来暂停游戏的确有些困难,这种情况下利用脚本来暂停游戏会比较方便。

请按下列代码修改 Player 脚本中的 Update 方法。

Player.Update 方法

void Update()
{
    if(this.is_landing) {
        if(Input.GetMouseButtonDown(0)) {
            this.is_landing = false;
            this.rigidbody.velocity = Vector3.up * this.jump_speed;
            Debug.Break();-------暂停游戏运行
        }
    }
}

修改后仅添加了 Debug.Break 方法的调用。播放器将在起跳的瞬间暂停游戏的运行(图 0.66)。

{%}

图 0.66 利用脚本暂停游戏

像这样,脚本可以通过调用 Debug.Break 方法来暂停游戏运行。不方便使用鼠标操作来暂停,或者“希望在游戏对象碰撞的瞬间暂停”时,可以试着使用这种方法。

暂停后画面将自动切换到场景视图,点击游戏标签可以重回到游戏视图。通过逐帧播放,我们可以看到在小方块和小球碰撞的瞬间,is_landing 的值变成 true 了(图 0.67)。

{%}

图 0.67 与小球接触的瞬间变量 is_landing 值的变化

在游戏暂停后,点击播放控件最右边的按钮就能让游戏继续逐帧播放。

现在我们已经搞清楚了 bug 的原因,下面就来考虑一下对策吧。当小方块发生碰撞时,不再无条件地将其设置为“着陆状态”,而只有在和地面碰撞时才设为“着陆”。为此首先就需要区分开碰撞对象是地面还是小球。这种情况下我们可以利用标签(图 0.68)。

{%}

图 0.68 添加标签

请选择项目视图中的 Floor Prefab。检视面板最上方附近有 Tag 文本显示。按下其旁边的 Untagged 按钮,将出现下拉菜单。点击菜单的最后一项 Add Tag...,检视面板中将显示 Tag Manager。

虽然被称为“标签管理器”,不过这里不但可以设定标签还可以设定层次。点击最上方的 Tags 文本左侧的三角形按钮,这时将显示出所有的标签。最开始时因为没有任何标签,所以 Size 值为 1。点击 Element0 文本右侧的输入框,输入 Floor。

完成了这些操作以后,再次从项目视图中选中 Floor Prefab,并点击检视面板中 Tag 旁的 Untagged。刚才添加的 Floor 就被添加到了下拉菜单的底部。请选中它,Tag 旁边的文本若变为 Floor,则说明标签设定完成。

接下来修改脚本。请按下列代码修改 Player.OnCollisionEnter 方法。这里删除了之前在 Player.Update 方法中添加的 Debug.Break();。

Player.OnCollisionEnter 方法

void OnCollisionEnter(Collision collision)
{
    if(collision.gameObject.tag == "Floor") {---------发生碰撞的对象如果是“Floor”(地面对象)……
        this.is_landing = true;
    }
}

使用了标签后就可以区分碰撞对象了。这样一来就只有在和地面碰撞时,也就是着陆时 is_landing 的值才会为 true(图 0.69)。

图 0.69 is_landing 的值正确地改变了

0.3.14 小结

本书的入门教程到这里就告一段落了。当然距离成品游戏还差很多,这里只不过实现了很少的一部分玩法。但尽管如此,我们还是能感觉到游戏中那种“跳起来把小球顶飞的爽快感”。

教程中的游戏灵感来源于“能不能把足球运动中的头球或者排球中的托球带入游戏中呢”这样的想法。能够像这样迅速地实践各种创意,应该说也是 Unity 的亮点之一吧。

教程中涉及的 Unity 的相关功能,都是在制作玩法原型阶段所必须掌握的最基础的知识。若要完成正式的游戏,仍有太多的 Unity 知识需要学习。不过,诸如添加脚本时的“拖曳”操作和选择预设时的“选择窗口的打开方法”等,都是 Unity 全体通用的方法。读者不必死记硬背各个功能,而应当掌握这些功能中相通的用法,这才是熟练掌握 Unity 的捷径。

0.4 C# 和 JavaScript 的对比 Tips

0.4.1 概要

下面我们将从 C# 和 JavaScript 的种种差异中挑选比较有代表性的几点进行对比解说。

一般而言,C# 比较适合大规模的游戏开发。相反,如果是没有编程经验的读者出于学习目的希望快速地开发出游戏,采用 JavaScript 或许更为合适。

随书下载文件中 Chapter0 文件夹下的 CSvsJS 项目里,分别采用 C# 和 JavaScript 开发了同一款游戏。其中,场景 GameScene 是 C# 开发的,场景 GameSceneJS 则是使用 JavaScript 开发的。

C#和JavaScript的对比

 

C#

JavaScript

类的定义

“class-类名 {”~“}”之间

文件全体

变量定义

bool is_landed = false;
类型 变量名 [= 初始值 ] ※

var is_landed : boolean = false;
var 变量名 [: 类型 ][= 初始值 ] ※

函数定义

float nantoka(int x)
返回值类型 函数名 ( 参数 )

function nantoka(x : int) : float
function 函数名 ( 参数 ) [: 返回值类型 ] ※

作用域

省略时默认为 private

省略时默认为 public

静态函数和静态变量的定义

public static float x;
public static void kantoka()
加上 static 关键字

public static var x : float;
public static function kantoka() : void
加上 static 关键字

泛型函数的调用

GetComponent ()
函数名 < 类型名 >()

GetComponent. ()
函数名 < 类型名 >()

Bool 类型

bool

boolean

字符串类型

string

String

数组

private string[] good_mess;
this.good_mess = new string[4]
this.good_mess[0] =“Nice!”;

private var good_mess : String[];
this.good_mess = new String[4];
this.good_mess[0] =“Nice!”;

※ 允许省略

0.4.2 类的定义

C# 中使用“class 类名{”和“}”包围的内容作为类的定义。

JavaScript 中 1 个文件就代表 1 个类,并不需要像 C# 那样指定类的范围。

{%}

图 0.70 类的定义

0.4.3 变量定义

C# 中采用“类型 变量名 = 初始值”的语法定义变量。“=”和初始值都可以省略。

JavaScript 采用“var 变量名 : 类型 = 初始值”的形式。和 C# 不同的是,变量定义必须以 var 开头,类型跟在变量名后,中间用“ :”隔开。不只是初始值,变量的类型也允许省略。

{%}

图 0.71 变量定义

在 JavaScript 中如果省略了变量的类型,解释器将在赋值的瞬间决定变量类型。这被称为动态类型

动态类型的例子

var flag;-----------------------------------------(1) 定义flag 变量

flag = false;--------------------------------(2) 用boolean 类型赋值
Debug.Log(flag.GetType().ToString());
flag = "off";---------------------------------(3) 用String 类型赋值
Debug.Log(flag.GetType().ToString());

执行上面的代码,控制台窗口将输出:

System.Boolean
UnityEngine.Debug:Log(Object)
System.String
UnityEngine.Debug:Log(Object)

可以看到每次赋值时,变量 flag 的类型都改变了。这里如果把(1)改为

var flag : boolean;

也就是固定了变量类型的话,代码运行时将报错:

Assets/Script/PlayerJS.js(14,16): BCE0022: Cannot convert 'String' to 'boolean'.( 无法将String 转换为boolean 类型)

这是因为系统已经无法动态决定类型了。

虽然动态类型有时候使用起来很方便,但也常因疏忽而对变量赋予错值。所以尽管稍微麻烦,还是推荐在定义中明确指定变量的类型

0.4.4 函数的定义

C# 中按照“返回值类型”“函数名称”“参数”的顺序声明函数。多个参数间用逗号分隔,并列写在括号 () 中。如果只有一个参数,则和定义普通变量一样,写成“类型 变量名”的形式。

JavaScript 中函数的定义由 function 关键字开始,后面接着“函数名”“参数”“返回值类型 ”。和 C# 一样,参数用括号括起来,用“参数名称 : 参数类型”的形式声明。多个参数的情况下用逗号隔开。返回值的类型声明是在函数名后加上“: 返回值类型”。函数参数和返回值的类型都可以省略。

{%}

图 0.72 函数的定义

0.4.5 作用域

在省略类方法和变量的作用域声明的情况下,C# 中默认作用域为 private,JavaScript 中默 认为 public

0.4.6 静态函数和静态变量的定义

C# 和 JavaScript 中都使用 static 修饰符。

{%}

图 0.73 静态函数和静态变量的定义

0.4.7 范型方法的调用

调用 GetComponment 之类的范型方法时,C# 和 JavaScript 中都必须用“<>”把类型名包起来。需要特别注意的是,JavaScript 中还需要在“<>”和函数名称之间添加一个点符号“.”。

{%}

图 0.74 泛型方法的调用

0.4.8 Bool 类型和字符串类型

表示真伪的布尔变量在 C# 中的类型名为 bool,而在 JavaScript 中则写作 boolean。请读者注意在拼写上有一点小小的差别。

C# 中的字符串类型是 string,而 JavaScript 中的字符串类型则是首字母大写的 String

0.4.9 数组

C# 和 JavaScript 都通过在类型名后加上 [ ] 来表示数组。另外通过“new 元素类型 [ 数组大小 ]”的语法来创建数组这一方法以及对数组元素的访问方法,在两门语言中都是相同的。

0.4.10 小结

编程语言林林总总,应该说 C# 和 JavaScript 之间的区别算是比较少的。对于熟悉其中一门编程语言的读者来说,只要注意以下几个要点:

  • 类定义的范围

  • JavaScript 中变量、函数参数和返回值的类型允许省略

  • varfunction 关键字

使用 Unity 来开发游戏就应该不成问题了。

不过,Unity 中的 JavaScript 和普通的 JavaScript 还有少许差异。通过 Unity 来学习 JavaScript 的读者请稍微留意。

虽然并无必要熟悉掌握这两种编程语言,不过试着对比它们之间的差异还是很有意义的。虽然本书推荐读者采用 C# 进行开发,不过官方的参考手册中有许多示例代码都由 JavaScript 写就。所以使用 C# 的开发人员,若能对 JavaScript 做一定程度的了解,还是会带来不少方便的。

0.5 关于预设 Tips

0.5.1 概要

预设是 Unity 开发中的必备技能之一。

在一般的编程和游戏开发环境中,并没有“预设”这种说法。这是 Unity 的专用术语。不过,与之类似的思想其实在许多程序中都早有体现。如果把预设简单地解释成“用于创建大量相同的物件而使用的模板”,估计很多读者都会有恍然大悟的感觉吧。

那么从现在开始,就让我们使用随书下载文件中 Chapter0 文件夹下的 AboutPrefab 项目,来进行一些简单的实验。看到了实际的效果之后,读者应该很快就能理解预设的特性。

0.5.2 改良“小方块”游戏对象

首先请用 Unity 打开 AboutPrefab 项目。可以看到有一个叫作 Cube 的游戏对象(以下称为小方块)。它和入门教程中所创建的小方块基本类似,我们可以调整跳起的高度。

从项目视图中选择 Player Prefab。检视面板中将出现 Player(Script) 标签。试着修改 Jump Height 的值(图 0.75),运行游戏后就会发现小方块跳起的高度改变了。

图 0.75  改良后的“小方块”游戏对象

在控制小方块动作的 Player 类中做如下修改,以根据最高点的高度计算出起跳速度。

Player 类(摘要)

public class Player : MonoBehaviour {
    public float JumpHeight = 4.0f;--------------跳跃高度


    void Update ()
    {
        if(this.is_landed) {
            if(Input.GetMouseButtonDown(0)) {
                this.is_landed = false;
                       ┌----------------------(1)起跳瞬间的速度(最高点为 JumpHeight 时的速度)
                float y_speed = Mathf.Sqrt(
                    2.0f * Mathf.Abs(Physics.gravity.y) * this.JumpHeight);

                this.rigidbody.velocity = Vector3.up*y_speed;
            }
        }
    }
}

在计算式(1)中,根据最高点的高度求出起跳瞬间的速度。用 v 表示起跳速度,h 表示最高点的高度,g 表示重力加速度,它们之间满足下列公式:

\sqrt{2*\text{g}*\text{h}}

具体原理可以查询相关的物理书籍。

0.5.3 预设与对象实例

从项目视图中选择 Player Prefab 并将其拖曳到场景视图中。这时将创建一个小方块的实例。请再次把该预设拖曳到场景视图中。算上最初创建的对象,现在一共有 3 个小方块(图 0.76)。从左向右分别设置它们的 X 坐标为-2.0、0.0、2.0,并将 Y 坐标和 Z 坐标设定为和最初创建的小方块相同。这里不需要做到完全一致,只要将它们排开,能够容易地区分出彼此即可。

{%}

图 0.76 创建小方块的实例对象

启动游戏。点击鼠标左键后,三个小方块将同时起跳(图 0.77)。

图 0.77 小方块的操作

层级视图中显示了三个 Player Prefab。这些正是在游戏画面中看到的小方块的实例。它们是内部构造完全一致的 Player Prefab 预设的副本,并且跳跃的高度 JumpHeight 都等于 4。

项目视图中显示的 Player Prefab 就是预设。预设就像是用来创建游戏对象的模板,只有实例化以后才能出现在游戏中。

{%}

图 0.78 实例和预设

0.5.4 预设和实例的变更

请在项目视图中选择 Player Prefab 后将 JumpHeight 的值由 4 改为 2。现在 3 个小方块的跳跃高度都变得比原来低。这是因为预设的属性变化反映到了所有由它生成的实例对象上(图 0.79)。

{%}

图 0.79 修改预设

由预设生成的实例对象,各个属性都和预设是一样的。修改了预设之后,所有实例化产生的游戏对象也会改变。根据这种现象我们可以知道由预设实例化生成的对象中含有预设的信息

接下来我们选择最左边的小方块,将 JumpHeight 的值由 2 改为 6。现在只有最左边的这个小方块改变了跳跃高度。和刚才不同,只有被选中小方块的 JumpHeight 值改变了。

像这样,直接修改实例并不会引起预设和其他由预设生成的实例发生变化(图 0.80)。

{%}

图 0.80 修改实例

接下来,选中刚才修改了的最左边的 Player Prefab 对象,在检视面板中按下 Apply 按钮。会发现其余两个实例化对象和预设的 JumpHeight 值都变为了 6(图 0.81)。

{%}

图 0.81 将实例的变化反映到预设上

一开始,我们验证了预设的变化会引起实例的变化。点击 Apply 按钮后则正好相反,实例的变更也会引起预设的变更。由于预设的变更又会反映到实例上,结果就导致了变化从最初的 1 个实例传递到了所有的实例。

0.5.5 小结

最后让我们对预设做个总结吧。难以理解“预设到底是个什么东西”的读者可以尝试这样类比:

  • 预设 = 印章

  • 从预设生成的游戏对象 = 印出的图案

假定插图画好以后,存在一种机器能把插图的图案刻成印章。这里绘制插图就好比创建游戏对象,而制作印章就相当于创建预设。按下印章后,纸上将出现和最初的插图相同的图案。这个按下印章的过程,就类似于预设的实例化(图 0.82)。

{%}

图 0.82 根据插图制作印章

制作好印章后,就可以印出任意多个相同纹理的图案。同样,通过提前预设游戏对象,也可以随时创建出任意多个相同的实例对象。

普通印章的情况下,即使重新雕刻了底面的图案,那些已经印出的图案也不会发生改变。而我们的这个印章则比较特殊,在底面图案改变后,印出的图案也会随之改变。很明显这个“神奇的印章”就是 Unity 的预设(图 0.83)。

{%}

图 0.83 改变印章底部的图案

接下来让我们再进一步对纸上已经印出的图案做出某些修改。可以发现印章底面的图案也变成了新的修改后的图案(图 0.84)。

{%}

图 0.84 修改印制好的图案

Unity 中通过点击检视面板的 Apply 按钮,可以把实例的变化反映到预设中。

相信读者现在应该大致理解 Unity 中的预设概念了。不妨参照“预设 = 印章”这个比喻,再次体验一下 Unity 预设的种种特性。要知道,亲手试验才是最有助于理解的学习方式哦!

目录