Unity-NGUI-指南-全-
Unity NGUI 指南(全)
原文:
zh.annas-archive.org/md5/493aa51abe201ba458e6d9b9f5c00c4e译者:飞龙
前言
本书献给 Next-Gen UI 工具包(也称为 NGUI)的初学者。您可能听说过这个 Unity 3D 插件;它因其易于使用和有效的 WYSIWYG 工作流程而受到开发者的欢迎。
NGUI 提供了内置组件和脚本,以创建您项目的精美用户界面,大部分工作都在编辑器内部完成。
通过本书,您将获得创建有趣用户界面的必要知识。本书的七个章节都是实用的,将指导您创建主菜单和 2D 游戏。
本书涵盖内容
第一章, NGUI 入门,描述了 NGUI 的功能和流程。然后我们将导入插件并创建我们的第一个 UI 系统,并研究其结构。
第二章, 创建小部件,介绍了我们的第一个小部件并解释了如何配置它。然后解释了如何使用小部件模板创建主菜单。
第三章, 优化您的 UI,解释了拖放系统以及如何创建可拖动窗口。它还涵盖了使用动画、可滚动文本和 NGUI 的本地化。
第四章, NGUI 中的 C#,介绍了 C# 事件方法和将用于创建工具提示、通知和 Tweens 的代码导向组件。
第五章, 构建可滚动视口,介绍了使用滚动条、键盘箭头和可拖动项目进行交互式全屏滚动的视口。
第六章, 图集和字体自定义,解释了您如何使用自己的精灵和字体自定义 UI;这将使我们能够修改整个主菜单的外观。
第七章, 使用 NGUI 创建游戏,涵盖了游戏功能,如生成移动敌人、处理玩家输入以及检测小部件之间的碰撞以创建游戏。
本书所需条件
为了跟随本书,您需要可从 unity3d.com/unity/download 获取的 Unity 3D 软件。
您可以使用任何版本的 Unity,但我推荐 4.x 循环。仅仅添加组件按钮和复制粘贴组件功能就能为您节省一些时间。您必须熟悉 Unity 的基本工作流程;GameObject、Layers 和 Components 这些词不应该对您来说是秘密。
所有与编码技能相关的代码都在此处提供,并对每一行代码进行了注释。因此,如果您不熟悉它们,您仍然能够理解它们。
在使用这本书的过程中,我们将创建自己的精灵。如果您不想或不能自己创建这些资产,请不要担心;我为这本书创建的资产将可供下载。
您还需要 Tasharen Entertainment 为 Unity 提供的 NGUI 插件。您可以直接从 Unity Asset Store 购买,或者您可以点击页面底部的“立即购买”按钮www.tasharen.com/?page_id=140。
本书面向对象
无论您是刚开始使用 Unity 3D 的初学者,还是中级开发者,或者是一位寻找有效 UI 解决方案的专业开发者,这本书都是为您准备的。
您已经为 PC、控制台或移动平台上的游戏或应用工作过,但您是否在 Unity 内置 UI 系统中挣扎,以创建您游戏的用户界面和菜单?这就是您应该所在的地方。
一旦您阅读完这本书,您会发现构建用户界面可以变得简单、快速且有趣!
习惯用法
在这本书中,您将找到多种文本样式,以区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码词如下所示:“声明一个新的Difficulty变量来存储当前难度。”
代码块如下设置:
public void OnDifficultyChange()
{
//If Difficulty changes to Normal, set Difficulties.Normal
if(UIPopupList.current.value == "Normal")
Difficulty = Difficulties.Normal;
//Otherwise, set it to Hard
else Difficulty = Difficulties.Hard;
}
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
//We will need the Slider
UISlider slider;
void Awake ()
{
//Get the Slider
slider = GetComponent<UISlider>();
//Set the Slider's value to last saved volume
slider.value = NGUITools.soundVolume;
}
新术语和重要词汇以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:“您现在可以点击创建您的 UI按钮。”
注意
警告或重要注意事项以如下框的形式出现。
小贴士
小贴士和技巧看起来像这样。
读者反馈
我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者的反馈对我们开发您真正能从中获得最大收益的标题非常重要。
要向我们发送一般反馈,只需发送一封电子邮件到<feedback@packtpub.com>,并在邮件的主题中提及书名。
如果您在某个主题上具有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在,您已经成为 Packt 书籍的骄傲拥有者,我们有许多事情可以帮助您从您的购买中获得最大收益。
下载示例代码
您可以从您在www.packtpub.com的账户下载所有已购买的 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
下载本书的颜色图像
我们还为您提供了一个包含本书中使用的截图/图表彩色图像的 PDF 文件。彩色图像将帮助您更好地理解输出的变化。您可以从www.packtpub.com/sites/default/files/downloads/8667OT_ColorGraphics.pdf下载此文件。
错误清单
尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何错误清单,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击错误清单提交表单链接,并输入您的错误清单详情来报告它们。一旦您的错误清单得到验证,您的提交将被接受,错误清单将被上传到我们的网站,或添加到该标题的错误清单部分。任何现有的错误清单都可以通过从www.packtpub.com/support选择您的标题来查看。
盗版
互联网上对版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过<copyright@packtpub.com>与我们联系,并提供疑似盗版材料的链接。
我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。
询问
如果您在本书的任何方面遇到问题,可以通过<questions@packtpub.com>联系我们,我们将尽力解决。
第一章. NGUI 入门
在我们导入插件并创建第一个 UI 之前,我们将讨论 NGUI 的整体工作流程。然后我们将查看 UI 的结构、重要参数和一般行为。
什么是 NGUI?
下一代用户界面套件是 Unity 3D 的插件。它具有易于使用、非常强大且与 Unity 内置 GUI 系统 UnityGUI 相比进行了优化的巨大优势。由于它是用 C# 编写的,因此易于理解,您可以根据需要对其进行调整或添加自己的功能。
NGUI 标准许可证的价格为 95 美元。购买此许可证,您将获得有用的示例场景。我建议您从舒适开始使用此许可证——免费评估版本是可用的,但它有限、过时,不推荐使用。
NGUI 专业许可证,价格为 200 美元,让您可以访问 NGUI 的 GIT 仓库,提前访问最新的测试版功能和发布。
2000 美元的 站点许可证适用于同一工作室内的无限数量开发者。
让我们概述一下此插件的主要功能,并看看它们是如何工作的。
UnityGUI 与 NGUI 的比较
使用 Unity 的 GUI,您必须通过在屏幕上显示标签、纹理或任何其他 UI 元素的代码中添加行来创建整个 UI。这些行必须写在每次调用时都会被调用的特殊函数 OnGUI() 中。这不再是必要的;使用 NGUI,UI 元素是简单的 GameObject!
您可以创建小部件——这是 NGUI 所称的标签、精灵、输入字段等——通过手柄或检查器移动它们、旋转它们并更改它们的尺寸。复制、粘贴、创建预制体以及 Unity 的工作流程中的所有其他有用功能也都可以使用。
这些小部件由相机查看,并渲染在您可以指定的层上。大多数参数都可以通过 Unity 的检查器访问,您可以直接在游戏窗口中看到您的 UI 看起来是什么样子,而无需按播放按钮。
图集
精灵和字体都包含在一个称为图集的大纹理中。只需几步点击,您就可以轻松创建和编辑您的图集。如果您没有图像来创建自己的 UI 资产,插件中附带了一些简单的默认图集。
该系统意味着对于由不同纹理和字体组成的复杂 UI 窗口,在渲染时将使用相同的材质和纹理。这导致整个窗口只有一个绘制调用。这,加上其他优化,使 NGUI 成为在移动平台上工作的完美工具。
事件
NGUI 还附带了一个易于使用的 C# 编写的事件框架。该插件附带了许多额外的组件,您可以将它们附加到 GameObject 上。这些组件可以根据触发哪些事件执行高级任务:悬停、点击、输入等。因此,您可以在保持配置简单的同时增强您的 UI 体验。代码更少,收获更多!
本地化
NGUI 自带本地化系统,使您能够轻松地通过点击按钮设置和更改 UI 的语言。所有字符串都位于.txt文件中:每种语言一个文件。
着色器
NGUI 支持光照、法线贴图和折射着色器,这些可以为您带来美观的效果。裁剪也是 NGUI 控制的一个着色器功能,用于显示或隐藏 UI 的特定区域。
我们现在已经介绍了 NGUI 的主要功能以及它作为插件对我们有用的方式,现在是时候将其导入 Unity 中。
导入 NGUI
在 Asset Store 购买产品或获取评估版本后,您必须下载它。执行以下步骤进行下载:
-
创建一个新的 Unity 项目。
-
导航到窗口 | Asset Store。选择您的下载库。
-
点击NGUI: Next-Gen UI旁边的下载按钮。
-
下载完成后,点击库中的 NGUI 图标/产品名称以访问产品页面。
-
点击导入按钮,等待弹出窗口出现。
-
选择NGUI v.3.0.2.unitypackage旁边的复选框,然后点击导入。
-
在项目视图中,导航到Assets | NGUI,然后双击NGUI v.3.0.2。
-
将出现一个新的导入弹出窗口。再次点击导入。
-
点击工具栏上的任何按钮以刷新它。NGUI 托盘将出现!
NGUI 托盘将看起来如下截图所示:

您现在已成功将 NGUI 导入到项目中。让我们创建您的第一个 2D UI。
创建您的 UI
我们现在将使用 NGUI 的 UI 向导创建我们的第一个 2D 用户界面。此向导将为 NGUI 工作添加所有必需的元素。
在我们继续之前,请将场景保存为Menu.unity。
UI 向导
通过在工具栏中导航到NGUI | 打开 | UI 向导来打开 UI 向导创建您的 UI。现在让我们看看 UI 向导窗口及其参数。
窗口
您现在应该有一个带有两个参数的弹出窗口:

参数
两个参数如下:
-
层:这是 UI 将显示的层
-
相机:这将决定 UI 是否会有相机,其下拉选项如下:
-
无:不会创建相机
-
简单 2D:使用正交投影相机
-
高级 3D:使用透视投影相机
-
分离 UI 层
我建议您将 UI 与其他常用层分开。我们应该按照以下步骤进行:
-
点击层参数旁边的下拉菜单。
-
选择添加层。
-
创建一个新的层并将其命名为
GUI2D。 -
返回 UI 向导窗口并选择此新的GUI2D层用于 UI。
您现在可以点击创建您的 UI按钮。您的第一个 2DUI 已经创建!
您的 UI 结构
向导已为我们场景创建了四个新的 GameObject:
-
UI 根(2D)
-
相机
-
锚点
-
面板
让我们现在详细审查每个参数。
UI Root (2D)
UIRoot组件将小部件缩放以保持它们的大小可管理。它还负责缩放样式——它将根据您指定的参数将 UI 元素缩放以保持像素完美或占用屏幕的相同百分比。
在层次结构中选择UI Root (2D) GameObject。它附有UIRoot.cs脚本。此脚本调整其附加的 GameObject 的缩放,以便您可以使用像素指定小部件坐标,而不是如图所示使用 Unity 单位:

参数
UIRoot组件有四个参数:
-
缩放样式: 以下是可以用的缩放样式:
-
像素完美: 这将确保您的 UI 始终尝试保持相同的像素大小,无论分辨率如何。在这种缩放模式下,一个 300 x 200 的窗口在 320 x 240 的屏幕上会显得非常大,而在 1920 x 1080 的屏幕上会显得非常小。这也意味着,如果您 UI 的分辨率小于屏幕分辨率,它将被裁剪。
-
固定大小: 这将确保您的 UI 将根据屏幕高度成比例调整大小。结果是,您的 UI 将不会是像素完美的,但会缩放以适应当前屏幕大小。
-
固定大小在移动设备上: 这将确保在移动设备上固定大小,在其他所有地方保持像素完美。
-
-
手动高度: 使用固定大小缩放样式,缩放将基于此高度。如果您的屏幕高度超过或低于此值,它将被调整大小以显示相同的大小,同时保持宽高比(宽度/高度成比例关系)。
-
最小高度: 使用像素完美缩放样式,此参数定义了屏幕的最小高度。如果您的屏幕高度低于此值,您的 UI 将调整大小。它将类似于将缩放样式参数设置为固定大小,并将手动高度设置为此值。
-
最大高度: 使用像素完美缩放样式,此参数定义了屏幕的最大高度。如果您的屏幕高度超过此值,您的 UI 将调整大小。它将类似于将缩放样式参数设置为固定大小,并将手动高度设置为此值。
注意
请将缩放样式参数设置为固定大小,并将手动高度设置为 1080。这将允许我们在 1920 x 1080 的任何屏幕尺寸上拥有相同的 UI。
尽管 UI 在不同分辨率上看起来相同,但由于缩放仅基于屏幕高度,宽高比仍然是一个问题。如果您想覆盖 4:3 和 16:9 的屏幕,您的 UI 不应太大——尽量保持它为正方形。否则,您的 UI 可能在某些屏幕分辨率上被裁剪。
另一方面,如果您想要 16:9 的 UI,我建议您仅强制执行此宽高比。让我们通过以下步骤现在为此项目执行此操作:
-
导航到编辑 | 项目设置 | 玩家。
-
在检查器选项中,展开分辨率和展示组。
-
展开支持的纵横比组。
-
只勾选16:9框。
既然我们已经看到了 UI 根的不同参数,让我们来讨论一下相机。
相机
在层次结构视图中选择相机GameObject。它附带了UICamera.cs脚本。此脚本必须附加到任何需要与 UI 交互的相机上。
其目的是发送有关 UI 元素(如附加到按钮的碰撞器)发生的事件的不同消息。一些更常用的事件是OnClick()和OnHover()。
如果你认为有必要,你可以有多个相机;例如,你可以有一个用于 2D 游戏 UI 元素的正交相机,以及一个用于 3D 暂停菜单的独立透视相机。
为了本书的目的,我们将只使用一个相机。
参数
UICamera.cs脚本具有大量参数,如下面的截图所示:

这些参数如下:
-
事件类型:选择此相机将发送哪种事件类型。
-
世界:这是用于与 3D 世界 GameObject 交互的
-
UI:这是用于与 2D UI 交互的
-
-
事件遮罩:选择将用于接收事件的层。
- 在我们的情况下,我们将将其设置为GUI2D,因为我们的 UI 将驻留在其上。
-
调试:这包括启用或禁用调试模式选项。此选项在出现不希望的行为时很有用。
- 启用:当调试启用时,当前悬停的对象将显示在屏幕的左上角
-
允许多点触控:这包括启用或禁用触摸模式选项,允许同时触摸。如果你想在移动平台上使用捏合缩放或其他此类手势,这是强制性的。
-
粘性按下:这包括启用或禁用粘性按下模式选项。
-
启用:如果你将手指从按下的按钮中拖出,它将保持按下状态,并且直到释放之前,其他元素都不会从该手指接收即将发生的事件
-
禁用:如果你将手指从按下的按钮中拖出,它将不再被按下,其他元素将接收来自该手指的即将发生的事件
-
-
粘性工具提示:这包括启用或禁用粘性工具提示模式选项。
-
启用:当鼠标移出小部件时,工具提示消失
-
禁用:当鼠标移出按下按钮时,工具提示立即消失
-
-
工具提示延迟:它包括在显示小部件的工具提示之前所需的静止时间(以秒为单位)。
-
射线投射范围:射线投射是从一个点向特定方向发射的不可见射线,如果遇到另一个对象则停止。摄像机使用从鼠标或触摸位置向摄像机前方方向的射线投射来检测碰撞和处理事件。如果您需要限制交互到一定范围,您可以设置此射线投射的范围。默认的-1值表示射线投射的范围将延伸到摄像机能看到的距离。
-
事件源:这些布尔值让您可以指定此摄像机监听哪些事件。
-
鼠标:这是用于鼠标移动、左右/中键点击和滚轮。
-
触摸:这是用于触摸设备的。
-
键盘:这是用于键盘输入。它使用
OnKey()事件。 -
控制器:这是用于基于摇杆的设备。它使用
OnKey()事件。
-
-
阈值:当您想要指定触发特定事件前的最小值时,这些值会很有用。这可能会因游戏/应用程序而异。
-
鼠标拖动:当鼠标按钮被按下(触发
OnPress()事件)时,此值确定鼠标必须移动多少像素才被认为是拖动,并发送OnDrag()事件到被拖动的对象。 -
鼠标点击:当鼠标按钮被按下(触发
OnPress()事件)时,此值确定鼠标在按钮释放时没有效果(不触发OnClick()事件)之前可以移动的像素距离。 -
触摸拖动:这与鼠标拖动相同,但用于基于触摸的设备。
-
触摸点击:这与鼠标点击相同,但用于基于触摸的设备。
-
-
轴和键:这些参数让您可以将 Unity 输入轴和键分配给 NGUI 的输入系统。
-
水平:这是水平移动的输入轴(左/右按键事件)。
-
垂直:这是垂直移动的输入轴(上/下按键事件)。
-
滚动:这是滚动的输入轴。
-
提交 1:这是验证的主要键码。
-
提交 2:这是验证的次要键码。
-
取消 1:这是取消的主要键码。
-
取消 2:这是取消的次要键码。
备注
您可以在任何时间通过导航到编辑 | 项目设置 | 输入来编辑 Unity 输入。
-
好的,我们已经看到了UICamera组件的主要参数。我们必须看看摄像机的锚点子项。
锚点
锚点用于将 GameObject 附加到摄像机视图内的同一区域。例如,您可以将其附加到屏幕的边缘或角落,或另一个小部件。
在层次结构视图中选择锚点GameObject。它附有UIAnchor组件。它配置为根据父摄像机在屏幕上居中内容。
在我们创建小部件之前,我们必须了解这些UIAnchor参数如何修改它们的放置行为。
参数
UIAnchor组件如以下截图所示有七个参数:

这些参数如下:
-
UI 相机: 这是确定我们的锚定边界所使用的参考相机。默认情况下,它设置为 UI 使用的相机。
-
容器: 如果您将 GameObject 拖放到此字段,它将覆盖相机锚定。如果您需要根据容器 GameObject 而不是相机来锚定面板或小部件,这可能很有用。您的内容将使用分配的容器的位置进行放置。
-
侧边: 您希望您的子 GameObject 是居中、附着到参考相机/容器的某一边或角落吗?您可以在此处选择您的锚定点。
-
半像素偏移: 您应该勾选此布尔值。它使小部件在 Windows 机器上的位置像素完美。
-
仅运行一次: 如果您的屏幕分辨率从不更改,或者您希望在开始时将其删除,则可以勾选此布尔值。结果,您的锚定将在开始时执行,然后删除并不再更新。
-
相对偏移: 这个
Vector2类接受介于-1 和 1 之间的两个值,以向最终位置添加相对偏移。对于X值为 0.12 和Y值为 0.32,它将在水平方向上偏移 12%,在垂直方向上偏移 32%——在任何分辨率下看起来都一样,因为偏移取决于屏幕大小。 -
像素偏移: 此参数类似于相对偏移,但它绝对而不是相对。您可以输入像素偏移——它将根据分辨率不同而看起来不同,因为偏移在所有屏幕大小上都将保持相同。
我们已经解释了不同的UIAnchor参数,但这个最后的子项,面板是什么?让我们来看看,然后我们将准备好创建我们的第一个小部件!
面板
在层次结构视图中选择面板GameObject。它附加了一个UIPanel组件。
面板的作用是仅在一个绘制调用中持有小部件并渲染几何形状。您可能需要创建多个面板来分割您的 UI,但每个新面板都会添加一个绘制调用。
参数
UIPanel.cs脚本如以下截图所示有八个参数:

这些参数如下:
-
Alpha: 您可以更改整个面板的透明度级别。所有子小部件都将受此 Alpha 值的影响,但嵌套面板不会。
-
深度: 这用于定义哪个面板覆盖另一个面板。深度值为 1 的面板将出现在深度值为 0 的面板之前。您可以使用后退或前进按钮更改深度,或者简单地在此字段中输入一个数字。面板也可以有负深度。
-
法线: 如果您需要它通过着色器对光照做出反应,则必须勾选此布尔值。它将为您的 UI 几何形状计算法线。
-
裁剪:这个布尔值允许在拖动面板时禁用子小部件的渲染,从而提高性能。
-
静态:如果您所有的面板小部件都是静态的并且永远不会移动,请选中此选项——这将提高性能!
-
显示所有:这将显示检查器视图中的所有绘制调用。
-
面板工具:这是一个面板管理工具。您可以在场景中可视化和选择每个面板。您可以通过Alt + Shift + P打开它,或者导航到NGUI | 打开 | 面板工具。这个面板会在面板工具参数中显示吗?对于通过代码动态创建的临时面板,如警告消息或弹药拾取通知,应该取消选中。
-
裁剪:这将允许我们隐藏给定矩形之外的小部件。当开启时,您可以使用
中心和大小参数选择裁剪矩形的尺寸。此矩形之外的所有内容都将被隐藏。-
无:无裁剪——整个面板将显示出来。
-
硬裁剪:裁剪已启用——对框外小部件的粗糙裁剪。
-
软透明度:裁剪已启用——带有淡出/淡入边框的软裁剪。
我们现在已经查看了UIPanel组件的参数,它将被用来容纳我们的小部件。
-
摘要
在本章中,我们讨论了 NGUI 的基本工作流程——它与 GameObject 一起工作,使用图集将多个纹理组合到一个大纹理中,具有事件系统,可以使用着色器,并且有本地化系统。
在导入 NGUI 插件后,我们使用 UI 向导创建了我们的第一个 2D UI,审查了其参数,并为我们的 UI 创建了自己的 GUI 2D 层。
最后,我们分析了 NGUI 为我们自动创建的四个 GameObject。在审查它们的参数后,我们可以总结它们的作用如下:
-
UI 根节点包含 UI 并将其缩放以实现像素完美或固定大小
-
相机视图查看 UI 并向小部件发送交互消息
-
锚点可以将元素附加到屏幕边缘或对象上,并添加偏移量
-
面板容纳我们的小部件并对其进行渲染,是否裁剪均可。
我们现在准备好创建我们的第一个小部件了。是时候进入下一章了。
第二章。创建小部件
在本章中,我们将创建我们的第一个精灵小部件,并了解它是如何工作的。然后我们将创建每个重要小部件模板的一个示例,并分析它们相应的参数,以便您知道如何创建和配置它们。
在本章结束时,我们将拥有一个功能齐全的主菜单,其中包含 NGUI 的大部分小部件。
创建我们的第一个小部件
我们将创建我们的第一个精灵小部件来显示主菜单的背景窗口。为了轻松完成此操作,NGUI 为我们提供了一个带有几个模板的Widget Wizard。
小部件向导
您可以通过导航到NGUI | Open | Widget Wizard来打开小部件向导。它看起来如下截图所示:

如您在前面的截图中所见,要创建一个小部件,您需要配置Atlas和Font。正如第一章中所述,“NGUI 入门”,一个 atlas 是一个包含您需要创建 UI 的精灵的大纹理。在本章的其余部分,我们将使用默认的 atlas,名为SciFi Atlas,它包含在插件中。
选择纹理图集
让我们选择我们的默认 SciFi 纹理图集,它包含必要的精灵,如下所示:
-
在Project视图中,导航到Assets | NGUI | Examples | Atlases | SciFi。
-
将预制件
SciFi Atlas.prefab拖放到Atlas字段中。 -
将预制件
SciFi Font – Header.prefab拖放到Font字段中。
我们已经选择了Atlas和Font预制件。现在我们可以从模板创建一个小部件。
从模板创建小部件
让我们通过以下步骤从模板创建一个小部件:
-
点击Template字段旁边的下拉菜单。
-
将Sprite选项选择为Template。
-
点击Sprite字段旁边的下拉菜单。
-
选择名为Dark的精灵。
-
将Pivot选项保留为Center。
-
确保您已在Hierarchy视图中选择了Panel。
-
点击Add To按钮。
好的,我们的精灵小部件已经创建完成了!
注意
小部件向导将新小部件添加为所选 GameObject 或面板的子项。如果您选择了错误的 GameObject,您仍然可以在创建后将其拖放到正确的 GameObject 中。
小部件变换
我们已经创建了第一个小部件:Sprite (Dark)。在Hierarchy视图中选择它,并尝试以下操作来更改其变换值。
移动小部件
在Scene视图中,您可以使用句柄移动小部件,或者您可以直接在Inspector视图的X、Y或Z参数中输入坐标。以下截图是带有三个参数可见的Scene视图:

你应该始终将Z坐标保留在 0。如果你需要将小部件放置在另一个小部件之后或之前,请使用检查器视图中的后退和前进按钮来控制小部件的深度。
小贴士
你可以通过在点击轴的手柄之前按住Shift来仅在一个轴上移动你的小部件。
旋转小部件
在场景视图中,将鼠标光标放在围绕小部件的任何蓝色圆圈的外侧。你的光标旁边将出现一个旋转图标。现在你可以按住左鼠标按钮并移动鼠标来旋转小部件。
小贴士
默认情况下,旋转设置为 15 度步长。如果你希望有更精确的旋转——1 度步长——旋转时请按住Shift键。
缩放小部件
你可能已经注意到,在检查器视图中,缩放值被灰色显示。这是因为你应该使用UISprite组件的尺寸参数。
在场景视图中,将鼠标光标放在围绕小部件的任何蓝色圆圈上。你的光标旁边将出现一个调整大小图标。现在你可以点击并拖动鼠标来调整小部件的大小。
小贴士
使用蓝色手柄不会保持你的小部件相对于当前位置居中。如果你想在小部件的两侧按比例调整大小,请点击尺寸组件的X或Y参数前面的空间,并左右拖动鼠标。
为了保持像素完美,你应该避免使用 Unity 的缩放工具上下调整小部件的大小。尽量使用尺寸来做所有事情。让我们看看我们还有哪些小部件参数。
常见小部件参数
选择(暗色)精灵,你将在检查器视图中找到以下截图中的参数:

这些参数适用于任何类型的小部件。让我们看看它们是什么:
-
颜色色调:这是小部件的启用 alpha 颜色。
-
剪贴板:如果你点击复制按钮,当前的颜色色调选择将被复制到这个参数。如果你点击粘贴按钮,剪贴板参数的选择将被粘贴到颜色色调。
-
枢轴:这显示了两组按钮,可以用来选择你希望小部件枢轴放置的角落或侧面。
-
深度:这可以用来显示你的小部件在其他人之前或之后。
-
尺寸:这可以用来以像素为单位显示大小而不是缩放。
现在我们已经看到了小部件参数,请为我们的新创建的(暗色)精灵输入以下尺寸:1300 x 850。
我们的小部件窗口变得非常大且丑陋。为什么?因为它是一个 15 x 15 的精灵被拉伸到 1300 x 850!让我们谈谈精灵,看看我们如何纠正这个问题。
精灵
我们已经创建了第一个精灵,并了解了如何移动、旋转和更改其尺寸。实际上,我们将其与原始尺寸相比放大了很多。但这个 15 x 15 的精灵有一些特别之处。
如果您选择精灵(暗色)GameObject,您将在检查器视图中的预览窗口内看到四条虚线。这意味着它是一个切片****精灵:

切片精灵
切片精灵是将图像分成九个部分,使其可调整大小同时保持其角落的比例。切片精灵可以按需缩放——它们仍然看起来很美。
由于精灵(暗色)是 9 切片精灵,我们必须告诉我们的UISprite组件将其视为此类。执行以下步骤:
-
选择精灵(暗色)GameObject。
-
点击其精灵类型字段旁边的下拉菜单。
-
选择切片。
注意精灵现在看起来有多好——它不再拉伸了!
备注
填充中心参数允许您只显示精灵的边缘,并在内部留下透明度。
尽管切片精灵非常适合窗口和可变大小的盒子,但您可以通过将精灵类型保留为简单来显示常规精灵。
平铺精灵
平铺精灵是一种旨在无限重复的图案——它可以用来通过重复相同的纹理覆盖大面积。现在让我们试试:
-
在层次结构视图中,将精灵(暗色)重命名为
Window。 -
选择我们的窗口GameObject 并执行以下步骤:
-
确保它位于
{0,0,0}位置,并且具有{1,1,1}的比例。 -
确保深度设置为
0。 -
在颜色色调参数中,将R更改为
115,G更改为240,B更改为255,A更改为255。
-
好的,我们有了我们的窗口。让我们通过以下步骤创建一个平铺背景,使其看起来更好:
-
选择我们的窗口GameObject 并使用Ctrl + D进行复制。
-
将副本重命名为
Background。 -
选择新的背景GameObject 并执行以下步骤:
-
将其 UISprite 的深度参数设置为
1。 -
点击精灵类型字段旁边的下拉菜单。
-
选择平铺。因为它看起来不好,因为我们的精灵不是平铺精灵。
-
点击精灵按钮。
-
选择蜂窝精灵,它是一个平铺精灵。
-
在颜色色调参数中,将R更改为
115,G更改为240,B更改为255,A更改为255。
-
-
通过导航到组件 | NGUI | UI | 拉伸并执行以下步骤,为它附加一个组件:
-
将窗口GameObject 拖放到容器字段中。
-
将样式字段设置为两者。
-
-
通过导航到NGUI | 附加 | 锚点并拖放到容器字段中,为它附加一个组件。
看起来更好了!我们现在有一个用于窗口背景的平铺精灵,它看起来如下:

我们使用了UIStretch组件来避免手动设置尺寸;如果您更改窗口的尺寸,背景将自动调整大小。
UIAnchor组件用于确保背景也随窗口移动。
填充精灵
一个填充的精灵对于创建生命条或进度条很有用;使用这个功能,你可以设置一个精灵并改变填充量参数在 0 到 1 之间来隐藏其一部分。以下截图显示了一个部分隐藏的进度条:

让我们按照以下步骤创建这个效果:
-
选择我们的背景GameObject 并将精灵类型设置为填充。
-
将填充方向设置为水平。
移动填充量滑块,你可以在游戏视图中看到它所做的工作。你已经理解了填充精灵系统。现在将精灵类型字段设置回平铺。
好的,我们已经看到了不同的小部件和参数。让我们学习如何添加文本。
标签
标签用于在屏幕上显示具有特定字体的文本,如下面的截图所示:

让我们按照以下步骤创建一个带有标签的小部件:
-
选择面板GameObject。
-
导航到NGUI | 打开 | 小部件向导。
-
选择标签模板。
-
我们已经选择了一个字体;它将用于此标签。
-
点击添加到按钮。
已在面板中添加了一个新的标签并将其放置在屏幕中央。
参数
选择标签后,UILabel参数将在检查器视图中显示。它们如下所示:
-
文本:这是一个大文本框,允许你输入要显示的文本。
-
溢出:当文本大于标签的尺寸时,这为小部件提供了四种不同的行为。这四种行为如下:
-
缩小内容:这会缩小文本以适应
-
限制内容:这确保溢出永远不会发生
-
自由调整大小:这会调整大小以显示所有内容及其溢出
-
调整高度:这仅调整高度——列样式
-
-
编码:如果你想通过插入[RRGGBB]十六进制值来更改字符的颜色,必须勾选此选项。
-
效果:这将帮助你为标签添加阴影或轮廓效果。你可以调整距离和颜色值。
-
最大行数:这是分配给标签的最大行数。对于无限行,请将其保留为0。
-
锚点:锚点还定义了文本的对齐方式。
创建标题栏
让我们添加一个标题,它将看起来像下面的截图:

请按照以下步骤继续创建这个窗口的标题栏:
-
选择面板并使用Alt + Shift + N创建一个新的子项。
-
将新子项重命名为
标题。它将成为我们的标题栏容器。 -
将标签GameObject 拖放到标题GameObject 中。
-
选择我们的标题GameObject。
-
导航到NGUI | 打开 | 小部件向导。
-
使用高亮精灵创建一个新的精灵并执行以下步骤:
-
将这个新的精灵(高亮)重命名为
背景。 -
将精灵类型设置为切片。
-
将枢轴选项更改为顶部(带有向上箭头的按钮)。
-
将其变换位置重置为
{0``,0,0}`。 -
在颜色色调参数中,将R更改为
95,G更改为255,B更改为150,A更改为200。 -
将深度设置为
2。
-
-
通过导航到组件 | NGUI | UI | 拉伸并执行以下步骤来为其附加组件:
-
将我们的窗口GameObject 拖放到容器字段中。
-
将样式设置为水平。
-
将其 UISprite 的Y维度设置为
62。
-
-
从标题中选择标签GameObject 并执行以下步骤:
-
将其文本更改为
[AAFFFF]主菜单。 -
将其溢出参数设置为自由调整大小。
-
输入深度为
3。
-
-
通过导航到NGUI | 附加 | 锚点并执行以下步骤来为其附加组件:
-
将标题中的背景GameObject 拖放到容器字段中。
-
将侧边参数设置为居中。
-
-
在层次结构视图中选择我们的标题GameObject。
-
通过导航到NGUI | 附加 | 锚点并执行以下步骤来为其附加组件:
-
将我们的窗口GameObject 拖放到容器字段中。
-
将侧边参数设置为顶部。
-
我们标题GameObject 的层次结构和检查器视图应该看起来像以下截图中的那样:

现在我们有一个看起来真的像窗口的窗口。我们使用了锚点来避免手动设置位置。现在让我们添加一些按钮!
按钮
使用 NGUI,按钮易于创建和配置。
让我们通过以下步骤创建第一个:
-
选择面板GameObject。
-
使用Alt + Shift + N创建一个新的子项并执行以下步骤:
- 将其重命名为
Buttons。它将成为我们的按钮容器。
- 将其重命名为
-
导航到NGUI | 打开 | 小部件向导并执行以下步骤:
-
在层次结构视图中选择按钮模板。
-
对于背景字段,选择名为按钮的精灵。
-
-
选择按钮GameObject,然后点击添加到按钮。
一个按钮已经被创建并居中在屏幕上。如果你查看层次结构视图,你会看到按钮由一个名为按钮的容器 GameObject 和两个子项组成:一个背景精灵和一个标签。这就是 NGUI 的工作方式;模板只是简单地组装组件和小部件。如果你想的话,你可以使用正确的组件在空 GameObject 上从头开始构建一个按钮。
点击播放按钮。你可以看到悬停和点击已经设置好了!关闭播放模式,选择新的按钮GameObject,并查看检查器视图。
交互式小部件附有一个盒子碰撞体,这个按钮也是如此。碰撞体用于检测与光标的碰撞。
参数
一个按钮有一个UIButton组件,它处理七个按钮参数:
-
目标:当用户悬停或按下按钮时,此 GameObject 将被转换和修改。默认情况下,这是 背景。
-
正常状态:这是没有任何操作发生时的颜色渐变。
-
悬停状态:这是用户的光标在按钮上时的颜色渐变。
-
按下状态:这是用户点击按钮时的颜色渐变。
-
禁用状态:这是按钮被禁用(无法点击)时的颜色渐变。
-
持续时间:这是状态之间转换的持续时间。
-
通知:这是允许你选择在按钮点击时调用的方法的参数。你必须首先将一个 GameObject 拖动到 通知 字段中。然后,将出现一个 方法 字段,列出 GameObject 附加的脚本方法,如下截图所示:
![参数]()
在前面的示例中,我将 面板 GameObject 拖动到 通知 字段中。我的 面板 GameObject 附加了一个 ButtonManager.cs 脚本——此脚本有一个 ButtonClicked() 方法。我现在可以在 方法 字段中选择它。它将在点击时被调用。
注意
只有 公共 方法且没有参数的才会显示在 通知 参数的 方法 字段中。
此按钮还附加了一个 PlaySound 组件。它允许你在选定的事件发生时选择要播放的音频剪辑。你可以编辑 音调 和 音量 参数。
注意
如果需要,你可以添加任意数量的 PlaySound 组件,以便在用户悬停或点击某个东西时播放声音,例如。
播放和退出按钮
我们现在将在窗口中添加两个按钮,用于退出或启动游戏。它们将显示如下截图所示:

我们需要一个将管理游戏的 GameObject。它将包含用于管理通用行为(如退出或启动游戏)的 GameManager.cs 脚本。让我们首先创建它;按照以下步骤进行操作:
-
在 层次结构 根目录处创建一个新的 GameObject,使用 Ctlr + Shift + N 并执行以下步骤:
- 将其重命名为
GameManager。
- 将其重命名为
-
创建并附加一个新的
GameManager.csC# 脚本到它,并执行以下步骤:-
打开这个新的
GameManager.cs脚本。 -
在这个新脚本内部,添加一个名为
ExitPressed()的新方法,代码行如下:public void ExitPressed() { //Exit Now Application.Quit(); }
-
提示
下载示例代码
你可以从你购买的所有 Packt 书籍的账户中下载示例代码文件。www.packtpub.com。如果你在其他地方购买了这本书,你可以访问 www.packtpub.com/support 并注册以将文件直接通过电子邮件发送给你。
现在退出方法已经准备好了,让我们按照以下步骤创建和配置两个按钮:
-
选择 按钮 GameObject 并执行以下步骤:
-
将其重命名为
Exit。 -
将 GameManager GameObject 拖动到 通知 字段中。
-
在方法字段中选择GameManager.ExitPressed。
-
在正常颜色色调参数中,将R更改为
185,G更改为255,B更改为255,并将A更改为255。 -
在悬停颜色色调参数中,将R更改为
0,G更改为220,B更改为255,并将A更改为255。
-
-
通过导航到NGUI | 附加 | 锚点,将其附加一个组件,并执行以下步骤:
-
将我们的窗口GameObject 拖入容器字段。
-
将边参数设置为左下角。
-
将像素偏移设置为{
135,60}。
-
-
选择我们的退出按钮的子背景GameObject,并执行以下步骤:
- 输入深度为
2。
- 输入深度为
-
选择我们的退出按钮的子标签GameObject,并执行以下步骤:
-
将其文本更改为
退出。 -
输入深度为
3。
-
好的,我们有了退出按钮。让我们按照以下步骤创建我们的播放按钮:
-
复制退出按钮并执行以下步骤:
-
将这个新复制的名称重命名为
播放。 -
点击 UIButton 旁边通知字段的减号按钮,从其中移除GameManagerGameObject。
-
将其边参数设置为右下角。
-
将像素偏移设置为{
-135,60}。
-
-
通过导航到组件 | NGUI | 示例 | 点击加载关卡,将其附加一个组件,并将其关卡名称字符串参数设置为游戏。
-
选择我们的播放按钮的子标签GameObject,并将其文本更改为
播放。
完美,现在如果您构建场景,您将拥有一个仅用一行代码即可工作的功能退出按钮!像素偏移参数保持按钮与窗口边界的相同距离,即使您更改分辨率或窗口尺寸。我们将在稍后创建游戏场景。
备注
图像按钮也可以使用小部件向导创建。它们与普通按钮相同,只是它们使用图像来代替正常、悬停、按下和禁用状态的颜色和缩放缓动。
文本输入
现在,我们将学习如何添加文本输入以创建昵称框。执行以下步骤:
-
选择面板GameObject,并使用Alt + Shift + N创建一个新的子项。然后将这个新子项重命名为
昵称。它将成为我们的昵称框容器。 -
导航到NGUI | 打开 | 小部件向导。
-
从项目视图,导航到NGUI | 示例 | 图集 | SciFi。
-
将SciFi Font – Normal预制件拖入字体字段。
-
选择输入模板。
-
将精灵(暗色)设置为背景参数。
-
-
在选择昵称GameObject 后,单击添加到按钮。
已向场景中添加了一个名为输入的新小部件。
参数
已创建一个输入GameObject。让我们看看它的检查器参数:
-
输入标签:这是用于此输入的文本标签。
-
非活动颜色:这是文本未选中时的文本颜色。
-
活动颜色:这是文本正在编辑时的文本颜色。
-
默认文本:这是 空白 或带有标签的默认文本。空白 将在选择 输入 GameObject 时删除标签的文本。
-
键盘类型:这允许授权不同的字符集。这也会更改移动平台上的键盘布局。
-
按 Tab 选择:将想要在按 Tab 键编辑输入时被选择的 GameObject 拖入此字段。
-
自动保存键:这使标签的文本能够自动保存到指定的
PlayerPrefs()键。 -
最大字符数:这是允许的最大字符数。
0表示无限。 -
光标字符:这是文本的末尾字符。
-
密码:如果您激活此布尔值,标签的字符将在屏幕上被 ***** 替换。
-
自动更正:这可以在移动平台上启用或禁用自动更正。
创建一个昵称框
让我们使用这个文本输入来创建一个看起来像以下截图的昵称框:

让我们创建前面截图中所见的昵称框。执行以下步骤以创建:
-
复制 窗口 GameObject 并执行以下步骤:
-
将新复制的名称重命名为
Background。 -
将其拖入 昵称 容器 GameObject。
-
输入 深度 为
2。 -
将 尺寸 设置为
440x120。
-
-
选择我们的 输入 GameObject 并执行以下步骤:
-
将其 盒式碰撞器 组件的中心重置为
{0,0,0}。 -
在 自动保存键 参数中输入
Nickname。 -
输入 最大字符数 为
25。
-
-
通过导航到 NGUI | 附加 | 锚点 来将其附加到组件上,并执行以下步骤:
-
将 Background GameObject 从 昵称 内拖动到 容器 字段中。
-
将 像素偏移 设置为
{0,-17}。
-
-
从 标题 复制 标签 子 GameObject 并执行以下步骤:
-
将其拖入 昵称 GameObject 内。
-
将其文本更改为
[AAFFFF]昵称。 -
将 背景 GameObject 从 昵称 内拖动到 容器 字段中。
-
将 侧边 参数设置为 顶部。
-
将 像素偏移 设置为
{0,-32}。
-
-
从 输入 选择 背景 子 GameObject 并执行以下步骤:
-
输入 深度 为
3。 -
将 轴点 设置为 中心(中间按钮 + 中间按钮)。
-
将 变换 字段的定位重置为
{0,0,0}。 -
在 颜色色调 参数中,将 R 设置为
100,G 设置为230,B 设置为255,A 设置为255。
-
-
从 输入 选择 标签 子 GameObject 并执行以下步骤:
-
输入 深度 为
4。 -
将 轴点 设置为 中心(中间按钮 + 中间按钮)。
-
将变换字段的定位重置为
{0,0,0}。 -
将 标签 GameObject 的文本更改为
在此处输入您的姓名。
-
-
选择 昵称 容器 GameObject。
-
为其附加一个组件并导航到NGUI | 附加 | 锚点并执行以下步骤:
-
将我们的窗口游戏对象拖入容器字段。
-
将侧边参数设置为顶部。
-
将像素偏移设置为
{0,-220}。
-
好的,我们有了昵称框。你的层次结构视图应该看起来像以下截图:

用户可以输入他的昵称,最多 25 个字符。如果你移动或更改窗口的尺寸,我们的框将移动以保持在同一位置。
滑块
现在让我们为用户添加一个可以移动和选择音量级别的音量滑块。
可用滑块模板,允许你通过在条上滑动滑块手柄轻松调整参数。按照以下步骤创建音量滑块:
-
选择面板游戏对象并使用Alt + Shift + N创建一个新的子对象。
-
将新子对象重命名为
Volume。它将成为我们的音量设置容器。 -
导航到NGUI | 打开 | 小部件向导并执行以下步骤:
-
选择滑块模板。
-
将暗精灵设置为空。
-
将光精灵设置为全开。
-
将高亮设置为滑块手柄。
-
-
在选择音量游戏对象后,点击添加到按钮。
参数
已创建滑块。它有 6 个参数,如下所示:
-
值:这是滑块的当前值,介于 0 和 1 之间。
-
步骤:这是完全填充或清空滑块的步骤数。
-
方向:这是滑块的填充方向,可以是水平或垂直。
-
前景:这是用于填充滑块的精灵。
-
滑块手柄:这是用于改变滑块值的精灵。通过将其设置为null,将创建一个简单的进度条(用户无法交互)。
-
通知:这是允许你选择在滑块值变化时调用的方法的游戏对象。当分配了游戏对象时,你可以选择在值变化时调用的方法。
创建音量滑块
我们可以使用这个滑块来创建我们的音量滑块,它将看起来像以下截图:

按照以下步骤创建它:
-
从昵称复制背景游戏对象并执行以下步骤:
-
将复制的对象拖入音量容器游戏对象内部。
-
将其尺寸设置为
320x135。
-
-
通过导航到NGUI | 附加 | 锚点附加一个组件并执行以下步骤:
-
将我们的窗口游戏对象拖入容器字段。
-
将像素偏移设置为
{-420,-90}。
-
-
从昵称复制标签游戏对象并执行以下步骤:
-
将其拖入音量游戏对象内部。
-
将我们的音量背景游戏对象拖入容器字段。
-
将其文本更改为
[AAFFFF]音量。
-
-
选择滑块游戏对象。
-
通过导航到NGUI | 附加 | 锚点来将其附加到组件上,并执行以下步骤:
-
将背景GameObject 从音量拖动到容器字段内。
-
将像素偏移设置为
{-100, -23}。
-
-
从滑块中选择背景GameObject 并执行以下步骤:
-
将深度设置为
3。 -
在颜色色调参数中,将R改为
80,G改为220,B改为85,并将A改为255。
-
-
从滑块中选择前景GameObject 并执行以下步骤:
-
将深度设置为
4。 -
在颜色色调参数中,将R改为
95,G改为255,B改为190,并将A改为255。
-
-
从滑块中选择滑块手柄GameObject 并执行以下步骤:
-
将深度设置为
5。 -
在颜色色调参数中,将R改为
100,G改为255,B改为250,并将A改为255。
-
好的,我们现在有一个漂亮的音量滑块!你的层次结构视图应该看起来像以下截图:

我们现在将使用一个新的脚本将其链接到游戏音量。让我们向主菜单添加一些音乐。首先,按照以下步骤将你选择的音频文件添加到 Unity 项目中:
-
选择我们的主摄像机GameObject。
-
通过导航到组件 | 音频 | 音频源来将其附加到组件上,并执行以下步骤:
- 将音乐文件从项目视图拖动到音频源参数的音频剪辑字段。
-
从音量中选择滑块GameObject 并执行以下步骤:
-
创建并附加一个新的
VolumeManager.csC#脚本。 -
打开这个新的
VolumeManager.cs脚本。
-
在这个新脚本中,我们首先需要声明和初始化必要的变量。添加以下变量声明和Awake()方法:
//We will need the Slider
UISlider slider;
void Awake ()
{
//Get the Slider
slider = GetComponent<UISlider>();
//Set the Slider's value to last saved volume
slider.value = NGUITools.soundVolume;
}
在这里,我们将滑块的值初始化为NGUITools.soundVolume,因为这个浮点数是持久的,并且将在场景间保存——即使你退出游戏。
现在,让我们创建一个OnVolumeChange()方法,每次滑块的值改变时,都会修改我们的 AudioListener 方法的音量:
public void OnVolumeChange ()
{
//Change NGUI's UI Sounds volume
NGUITools.soundVolume = UISlider.current.value;
//Change the Game AudioListener's volume
AudioListener.volume = UISlider.current.value;
}
好的,方法已经准备好了。我们只需要在滑块的值每次改变时调用它。让我们使用UISlider组件的通知字段如下:
-
从音量中选择滑块GameObject 并执行以下步骤:
-
将滑块GameObject 从音量拖动到通知字段。
-
对于方法字段,选择
VolumeManager.OnVolumeChange。
-
现在,每次滑块的值被修改时,我们的方法将被调用。
你可以点击播放按钮;游戏音量会随着滑块的变化而变化。音量即使在退出游戏并重新启动时也会保存!
切换
现在我们有了音量滑块,让我们添加一个启用/禁用声音的复选框,这将把音量调至 0 并隐藏我们的音量滑块。
首先,创建一个如下所示的切换小部件:
-
选择面板GameObject 并使用Alt + Shift + N创建一个新的子组件。
-
将新子对象重命名为
Sound。它将成为我们的声音切换容器。 -
导航到NGUI | 打开 | 小部件向导并执行以下步骤:
-
选择Toggle作为模板。
-
选择深色精灵作为背景。
-
选择X精灵作为勾选标记。
-
在选择声音容器后,点击添加到按钮。
-
如下截图所示,已创建了一个带有标签的复选框:

参数
选择我们新的Toggle游戏对象。让我们看看 UIToggle 的检查器参数:
-
分组:这是切换的分组。同一分组的切换将作为单选按钮操作;一次只能选择其中一个。
-
起始状态:这定义了切换在开始时的状态。
-
动画:这是当复选框状态改变时播放的动画。
-
精灵:这让我们可以选择用作勾选标记的部件;在这里我们应该使用我们的X精灵。
-
过渡:这可以是平滑或立即;使用 alpha 淡入/淡出。
-
通知:这是在切换时通知的 GameObject。当分配 GameObject 时,您可以选择在切换事件上调用公共方法。
创建声音切换
我们已经看到了 UIToggle 的参数。现在我们将创建如下截图所示的这个声音切换:

让我们使用最近添加的Toggle游戏对象来创建这里显示的窗口。按照以下步骤操作:
-
从音量中选择背景和标签游戏对象并执行以下步骤:
-
复制它们。
-
将那些新复制的副本拖放到我们的声音容器内。
-
-
从声音中选择背景游戏对象并输入其 UIAnchor 的像素偏移参数为
{-420, 43}。 -
从声音中选择标签游戏对象并将其文本更改为
[AAFFFF]声音。 -
选择Toggle游戏对象并检查UIToggle中的起始状态布尔值。
-
通过导航到NGUI | 附加 | 锚点附加组件并执行以下步骤:
-
将声音中的背景游戏对象拖放到容器字段中。
-
在其像素偏移参数中输入
{-38, -20}。
-
-
通过导航到NGUI | 交互 | 切换对象并添加组件,将我们的音量容器游戏对象拖放到激活数组中。
-
从Toggle中选择背景精灵游戏对象并执行以下步骤:
-
输入深度为
3。 -
在颜色色调参数中,将R更改为
130,G更改为255,B更改为130,A更改为255。
-
-
从Toggle中选择勾选标记精灵游戏对象并执行以下步骤:
-
输入深度为
4。 -
在颜色色调参数中,将R更改为
50,G更改为255,B更改为70,A更改为255。
-
-
从Toggle中选择标签游戏对象并执行以下步骤:
-
输入深度为
3。 -
将其文本更改为
[AAFFFF]Enabled。 -
在 Color Tint 参数中,将 R 更改为
200,G 更改为255,B 更改为250,A 更改为255。
-
点击播放按钮。我们有一个带声音切换复选框的漂亮的声音框,当需要时可以隐藏/显示 Volume 框。但它还没有关闭声音。
我们需要对 VolumeManager.cs 脚本进行一些修改以纠正这个问题。
首先,打开我们的 VolumeManager.cs 脚本。我们将添加一个新的 OnSoundToggle() 方法,当切换状态改变时将被调用。它将直接将音量设置为 0,或设置为滑块的值。将此新方法添加到 VolumeManager.cs 中,如下代码行所示:
public void OnSoundToggle()
{
float newVolume = 0;
//If sound toggled ON, set new volume to slider value
if(UIToggle.current.value)
newVolume = slider.value;
//Apply newVolume to volumes
AudioListener.volume = newVolume;
NGUITools.soundVolume = newVolume;
}
好的,前面的方法将根据切换的状态将我们的音量设置为 0 或滑块的值。让我们通过选择我们的 Toggle GameObject 并将 Volume 中的 Slider GameObject 拖到 UIToggle 的 Notify 字段中,在 On Value Change 部分下面,将其链接到我们的声音切换。然后,对于 Method 字段,选择 VolumeManager.OnSoundToggle。
点击播放按钮。就这样。当我们点击 Volume 中的 Toggle 复选框时,音量会相应地反应。
但如果我们使用切换关闭声音并停止运行,当我们再次点击播放时,复选框仍然被选中,音量滑块显示,但音量在 0。
这是因为我们的音量设置为 0,但复选框在开始时仍然被选中。让我们添加一行简单的代码,将起始状态设置为 false,如果音量在 0:
-
打开我们的
VolumeManager.cs脚本。 -
声明一个新的全局变量
public UIToggle soundToggle。 -
在
Awake()方法的末尾添加以下代码行://If volume is at 0, uncheck the Sound Checkbox if(NGUITools.soundVolume == 0) soundToggle.value = false; -
保存脚本并返回到 Unity。
-
从 Volume 中选择 Slider GameObject。
将 Volume 中的 Toggle GameObject 拖到音量管理器的 Sound Toggle 字段中。
点击播放按钮。就这样。如果你使用切换禁用声音并退出播放模式然后再次启动,复选框保持未选中状态,音量滑块不显示。完美!
弹出列表
我们现在将学习如何创建弹出列表,查看其参数,并为我们的游戏创建一个难度选择器。
-
选择 Panel GameObject 并使用 Alt + Shift + N 创建一个新的子项。
-
将新子项重命名为
Difficulty。它是我们的难度框容器。 -
导航到 NGUI | Open | Widget Wizard 并执行以下步骤:
-
选择 Popup List 作为 Template
-
将 Foreground 选择为 Dark。
-
将 Background 选择为 Dark。
-
选择 Highlight 精灵作为 Highlight。
-
-
在选择我们的 Difficulty GameObject 后,点击 Add To 按钮。
参数
刚刚创建了一个 Popup List GameObject。让我们看看它的参数:
-
Atlas:这是用于弹出列表精灵的图集。
-
Font:这是用于弹出列表选项的字体。
-
Text Label:这是在 Popup List 更改选择时更新的标签。
-
选项:这是将要弹出的选项列表——每行一个。
-
Default:这是启动时选择的选项。
-
位置:您可以强制选项列表出现在弹窗列表按钮的 Above 或 Below。如果此参数设置为
Auto,NGUI 将根据可用空间选择其中之一。 -
Localized:这启用了选项的本地化。
-
Background:这是弹窗列表选项容器的背景精灵。
-
Highlight:这是当前悬停选项的精灵。
-
Text Color:这是选项列表的文本颜色色调。
-
Background:这是弹窗列表的背景颜色色调。
-
Highlight:这是悬停选项的背景颜色色调。
-
填充:这是 X 和 Y 选项的填充。
-
Text Scale:这是选项的文本缩放。
-
Animated:如果未勾选,选项的显示将是瞬间的。
-
Notify:这是允许您选择在所选选项更改时调用的方法的 GameObject。
在 UIPopup List 组件下方,我们有之前已经见过的 UIButton 和 UIPlay Sound 组件。
注意
Widget Wizard 中也提供了一个弹窗菜单模板。唯一的区别是,菜单不会指示您已选择了哪个选项;按钮的标签不会更新。
创建难度选择器
我们现在将使用我们新的 Popup List GameObject 来选择游戏难度,如下截图所示:

按照以下步骤创建此难度选择器:
-
从 Sound 中选择 Background 和 Label GameObject 并执行以下步骤:
-
复制它们。
-
将它们拖放到我们的 Difficulty 容器中。
-
-
从 Difficulty 中选择 Background GameObject,并输入其 Pixel Offset 参数为
{420,43}。 -
从 Difficulty 中选择 Label GameObject 并将其文本更改为
[AAFFFF]Difficulty.。 -
在 Hierarchy 视图中选择我们的 Popup List 并执行以下步骤:
-
将其重命名为
Popup。 -
输入
Normal和Hard,用一行分隔。 -
在 Text Color 中,将 R 改为
190,G 改为250,B 改为255,A 改为255。 -
在 Background 中,将 R 改为
70,G 改为250,B 改为255,A 改为255。 -
在 Highlight 中,将 R 改为
70,G 改为255,B 改为150,A 改为255。 -
在 Hover 中,将 R 改为
70,G 改为255,B 改为150,A 改为255。
-
-
通过导航到 NGUI | Attach | Anchor 为其附加一个组件,并执行以下步骤:
-
将 Difficulty 中的 Background GameObject 拖放到 Container 字段。
-
输入 Pixel Offset 为
{-76,-20}。
-
-
选择我们弹窗的 Sprite GameObject 并执行以下步骤:
-
在颜色色调中,将R改为
170,G改为255,B改为190,A改为255。 -
将深度设置为
3。
-
-
选择我们弹窗的标签GameObject 并执行以下步骤:
-
在颜色色调中,将R改为
135,G改为255,B改为170,A改为255。 -
将深度设置为
4。
-
好的,我们现在有一个弹窗列表GameObject,它允许我们选择游戏的难度级别。你的层次结构面板应该看起来像以下截图:

现在是时候将其链接到一个考虑难度级别的方法了。通过执行以下步骤来实现:
-
打开我们的
GameManager.cs脚本。 -
按照以下方式声明一个新的枚举来存储我们的难度级别:
public enum Difficulties { Normal, Hard } -
声明一个新的
Difficulty变量来存储当前的难度级别,如下所示:public static Difficulties Difficulty = Difficulties.Normal;
我们使用了一个静态变量,因为它在加载游戏场景时不会被销毁。默认情况下,难度级别设置为正常。
现在我们需要添加一个OnDifficultyChange()方法,当弹窗列表状态改变时,它会更改我们的难度变量,如下所示:
public void OnDifficultyChange()
{
//If Difficulty changes to Normal, set Difficulties.Normal
if(UIPopupList.current.value == "Normal")
Difficulty = Difficulties.Normal;
//Otherwise, set it to Hard
else Difficulty = Difficulties.Hard;
}
我们的方法已经准备好了;我们需要在弹窗列表状态改变时调用它。通过执行以下步骤来实现:
-
保存所有修改后的脚本并返回 Unity。
-
从难度中选择弹窗列表GameObject 并执行以下步骤:
-
将我们的GameManagerGameObject 拖入通知字段。
-
对于方法字段,选择
GameManager.OnDifficultyChange。
-
现在,弹窗列表GameObject 将根据其值更改难度变量。一旦我们进入游戏,我们就能访问这个静态变量。
总结
在本章中,我们学习了如何创建和配置 NGUI 的大多数小部件——精灵、标签、按钮、文本输入、滑块、切换和弹窗列表。
我们现在有一个带有交互元素的主菜单。我们还使用了 NGUI 的通知事件系统来在代码中更改变量并注册用户的选项。
我们使用了UI 锚点和UI 拉伸组件来正确地定位我们的小部件——我们只需要移动每个框的背景精灵就可以移动整个元素。这比手动移动每个 GameObject 要有效得多!你应该有一个看起来像以下截图的主菜单:

好的,现在是我们增强 UI 体验并使其变得更好的时候了。让我们继续到第三章,增强你的 UI。
第三章。增强你的 UI
在本章中,我们将学习如何通过使用更高级的功能来增强我们的 UI 体验,具体如下:
-
可拖动面板和动画
-
拖放系统
-
自动内容对齐
-
剪切
-
可滚动文本
-
本地化系统
让我们先谈谈 NGUI 组件及其整体行为。
NGUI 组件
在 第二章,创建小部件,我们向我们的小部件添加了 UIStretch 和 UIAnchor 组件,以及 点击加载级别 组件。还有许多其他组件,本章的目的是讨论这些组件。我们将在本章中使用最重要的组件。当然,可以将组件添加到任何类型的小部件中。
这种面向组件的结构使 NGUI 非常灵活和模块化。我们将从将我们的主菜单制作成可拖动窗口开始。
可拖动面板
现在,我们将学习如何将我们的菜单转换成可拖动窗口。让我们添加正确的组件并研究其参数,如下所示:
-
选择 面板 游戏对象。
-
将其重命名为
MainMenu。 -
通过导航到 组件 | NGUI | 交互,将 可拖动面板 组件附加到它上。
MainMenu 游戏对象现在已附加了一个 UIDraggable Panel 组件。
参数
以下是在 UIDraggable Panel 中设置值的 13 个参数:
-
拖动效果:这是在拖动面板时使用的效果,以实现更平滑的拖动。
-
限制在面板内:这使用父面板来限制可拖动面板在其剪切边界内。
-
如果适合则禁用拖动:如果内容适合父面板的剪切边界,则将禁用拖动。
-
平滑拖动开始:这避免了拖动开始时的“跳跃”效果。
-
重新定位剪切:这立即将 剪切 重新定位到 左上角。
-
iOS 拖动模拟:当超过剪切边缘时,拖动移动速度会降低。
-
滚动轮因子:如果你想使滚动轮在 y 轴上拖动面板,将此值设置为大于 0。
-
动量量:这是当面板放下时应用的效果。释放滚动轮后,面板将继续移动。
-
水平滚动条:这允许你拖动一个滚动条来将其定义为面板的水平滚动条。
-
垂直滚动条:这允许你拖动另一个滚动条来将其定义为面板的垂直滚动条。
-
显示滚动条:这允许你始终显示滚动条,仅在需要时显示它们,或者拖动时显示。
-
缩放:这定义了面板应该拖动的轴:
0表示不允许拖动,1允许在此轴上完全拖动。 -
重置时的相对位置:这是相对于鼠标位置的偏移量。如果你想在拖动时从鼠标位置偏移,这很有用。
现在我们已经看到了组件的参数,让我们使用它们来拖动我们的主菜单。
拖动主菜单
我们已经添加了将此 UIPanel 设置为 Draggable Panel 的 UIDraggable Panel 组件。现在,我们必须标记我们的 MainMenu 为包含可拖动内容的 GameObject。
我们还将添加一个 Box Collider 组件来定义用户必须点击以拖动面板的位置:
-
选择我们的 MainMenu GameObject 并执行以下步骤:
-
将 UIPanel 的 Clipping 参数设置为 Alpha Clip。
-
在 Clipping 中的 Size 字段设置为
1920x1080。 -
取消选择 IOS Drag Emulation 布尔值。
-
-
向它添加 Drag Panel Contents 组件。
-
向它添加 Box Collider 组件并执行以下步骤:
-
选择 Is Trigger 布尔值——我们不需要碰撞,只需要一个触发器来接收来自 UICamera 的射线投射。
-
将其 中心 坐标设置为
{0,395,0}。 -
将其 大小 坐标设置为
{1300,62,1}。
-
点击播放按钮。通过点击窗口的标题,你可以拖动主菜单。但仍然配置不正确;你只能在 x 轴上移动它。
让我们通过执行以下步骤更改 MainMenu 的 UIDraggable Panel 组件的一个重要参数,以允许在 x 轴上拖动:
-
选择我们的 MainMenu GameObject。
-
在 UIDraggable Panel 中设置 Scale 为
{1,1,0}。
就这样!我们的 MainMenu 现在可以在两个轴上拖动了。如果你将其拖出屏幕,放下时它会自动回到屏幕内。我们必须定义屏幕大小的剪辑,以便此 Restrict Within Panel 功能能够工作。
拖放系统
我们现在将创建自己的 拖放 系统,该系统将使用户能够选择一个能力。他们可以在以下截图所示的选框内拖动两个可用能力之一:

能力选择
让我们创建一个拖放系统来选择玩家可以选择的两个能力之一:一个 炸弹 能力,它将爆炸;或者一个 时间 能力,它将使时间慢几秒钟。
可拖动物品容器
让我们首先创建一个漂亮的盒子来放置我们的能力,并通过执行以下步骤创建一个可拖动物品容器:
-
选择我们的 MainMenu GameObject 并执行以下步骤:
-
通过按 Alt + Shift + N 创建一个新的空子 GameObject。
-
将其重命名为
Powers。
-
-
从 Sound 中选择 Background 和 Label GameObject:
-
复制它们。
-
将这些新的副本拖放到我们的 Powers GameObject 内部。
-
-
在 Powers 中选择 Background GameObject 并执行以下步骤:
-
将其 尺寸 设置为
320x420。 -
在 UIAnchor 中设置 像素偏移 为
{0,-100}。
-
-
在 Powers 中选择 Label GameObject:
-
将其重命名为
TitleLabel。 -
将其文本更改为
[AAFFFF]Powers。
-
-
选择作为 Title 的子对象的 Label GameObject 并执行以下步骤:
-
复制它。
-
将这个新复制的命名为
SelectedLabel。 -
将它拖动到我们的Powers GameObject 内部。
-
将我们的Background GameObject 在Powers中拖动到其UIAnchor的Container字段。
-
将其UIAnchor的Side参数设置为Top。
-
在UIAnchor中将它的Pixel Offset设置为
{0,-95}。 -
将其Font更改为
SciFi Font – Normal。 -
将其文本更改为
[AAFFFF]Selected。
-
-
在Powers中选择我们的SelectedLabel GameObject 并执行以下步骤:
-
复制它。
-
将这个新复制的命名为
AvailableLabel。 -
将其文本更改为
[AAFFFF]Available。 -
在UIAnchor中将它的Pixel Offset设置为
{0,-295}。
-
-
在Powers中选择我们的SelectedLabel GameObject并执行以下步骤:
-
复制它。
-
将这个新复制的命名为
InstructionsLabel。 -
将文本更改为
[55AA99]Drag Power Here—每行一个单词。 -
将其Overflow参数设置为Shrink Content。
-
将其Depth设置为
4。 -
将其Dimensions设置为
128x45。 -
在UIAnchor中将它的Pixel Offset设置为
{0,-175}。
-
-
在Powers中选择我们的Background sprite GameObject 并执行以下步骤:
-
复制它。
-
将这个新复制的命名为
PowersContainer。 -
将其Dimensions设置为
215x90。 -
将其Color Tint设置为
{100,100,100,255}。 -
将其Depth设置为
3。 -
将我们的AvailableLabel GameObject 从Powers拖动到其UIAnchor的Container字段。
-
在UIAnchor中将它的Pixel Offset设置为
{0,-60}。
-
好的,我们现在有了带有正确标签和背景的权力盒。
可拖动物品
现在我们有了 PowersContainer,让我们创建以下两个可拖动物品:

按照以下步骤创建它们:
-
在Powers中选择我们的PowersContainer GameObject。
-
使用 Alt + Shift + N 创建一个新的子 GameObject 并命名为
Bomb。 -
通过导航到NGUI | Attach将其附加到一个Collider对象上。它将通过以下步骤检测鼠标并接收拖放系统的正确消息:
-
检查Is Trigger布尔值。
-
将新Box Collider的Size字段设置为
{90,90,1}。
-
-
通过导航到Component | NGUI | Interaction将其附加到Drag Object组件上。
-
将我们的Bomb从PowersContainer的Target字段拖动过来。
-
将其Scale设置为
{1,1,0}。 -
将Momentum Amount设置为
0。
-
-
在Powers中选择并复制Background sprite GameObject。然后执行以下步骤:
-
将它拖动到我们新的Bomb GameObject 内部。
-
将其Depth设置为
5。 -
将其Dimensions设置为
90x90。 -
移除其UIAnchor组件。
-
将其Transform位置值重置为
{0,0,0}。
-
-
在Powers中选择并复制AvailableLabel GameObject。然后执行以下步骤:
-
将那个新复制的命名为
Label。 -
将它拖动到我们新的Bomb GameObject 内部。
-
将其文本更改为
[AAFFFF]Bomb。 -
将其Depth设置为
6。 -
移除其UIAnchor组件。
-
将其变换位置值重置为{
0,0,0}。
-
-
选中我们的炸弹GameObject。
-
通过导航到组件 | NGUI | 交互来将其附加一个按钮颜色组件,并执行以下步骤:
-
将我们的背景GameObject 从炸弹拖动到目标字段。
-
将其按下颜色设置为{
0,255,0,150}。
-
-
创建一个新的
DragItem.csC#脚本并将其附加到按钮颜色组件。
我们有一个带有DragItem.cs脚本附加的可拖动炸弹力量。让我们通过以下步骤创建第二个时间力量:
-
在PowersContainer中选中并复制我们的炸弹GameObject。
-
将其重命名为
Time。 -
选中我们的新标签GameObject,它是时间的子对象。
-
将其文本更改为
[AAFFFF]时间。
好的,我们现在有两个可拖动的力量,并且它们重叠在一起。让我们使用锚点和网格组件来纠正这个问题,这将自动对齐我们的项目。我们可以使用以下步骤来完成:
-
选中我们的PowersContainerGameObject。
-
通过按Alt + Shift + N创建一个新的子对象,并将其重命名为
Grid。 -
通过导航到组件 | NGUI | 交互来将其附加一个网格组件。
-
将我们的炸弹和时间GameObject 拖动到我们新的GridGameObject 中。
-
选中我们的GridGameObject 并执行以下步骤:
-
将其单元格宽度设置为
105。 -
选中排序布尔值。
-
选中立即重置位置布尔值以更新表格。
-
将其变换位置设置为{
-52,0,0}。
-
UIGrid 组件自动对其子对象进行对齐。我们现在有两个可拖动的力量对齐。如果你点击播放,你会看到你可以像以下截图所示那样拖动它们:

掉落表面
我们将创建一个新的SurfaceGameObject,并附加一个DropSurface.cs脚本和一个Box Collider组件来定义可拖动物品可以放置的位置。
当用户将带有DragItem组件的对象拖放到SurfaceGameObject 上时,DragItem组件将被销毁,并且将实例化一个“掉落版本”的对象,作为SurfaceGameObject 的子对象。
首先,让我们通过以下步骤创建和配置SurfaceGameObject:
-
选中并复制我们的背景GameObject 从Powers,并将新副本重命名为
Surface。 -
从Powers中选择我们的新SurfaceGameObject 并执行以下步骤:
-
将其精灵更改为高亮。
-
将其颜色色调更改为{
0,25,5,255}。 -
将其深度设置为
3。 -
将其尺寸设置为
130x130。 -
将我们的SelectedLabelGameObject 从Powers拖动到其UIAnchor中的容器字段。
-
将其像素偏移设置为{
0,-80}。
-
-
通过导航到NGUI | 附加 | Collider来将其附加一个Collider对象。它将检测DragItems。然后执行以下步骤:
-
选中其是否触发布尔值。
-
将其大小设置为
{130,130,1}。
-
-
创建并附加一个新的
DropSurface.csC#脚本。
好的,我们的表面现在已准备好检测我们的物品。
放置时实例化的预制体
现在,我们需要为我们的能力创建两个预制体,当在SurfaceGameObject 上放置DragItem组件时,它们将被实例化为该 GameObject 的子组件。它们将如下所示:

让我们按照以下步骤创建这些预制体:
-
从网格中选择炸弹GameObject,然后按照以下步骤操作:
-
复制它。
-
将其重命名为
SelectedBomb。
-
-
从网格中选择我们新的SelectedBombGameObject。然后执行以下步骤:
-
将其正常颜色色调更改为R:
0,G:145,B:60,和A:255。 -
移除其盒子碰撞器组件。
-
移除其Drag Item组件。
-
-
创建并附加一个新的
Power.csC#脚本。 -
从SelectedBomb中选择背景sprite GameObject,然后执行以下步骤:
-
将精灵更改为光。
-
将其深度设置为
4。 -
将其尺寸设置为
120x120。
-
-
从SelectedBomb中选择标签GameObject,并将其深度设置为
5。 -
将我们的SelectedBombGameObject 拖放到项目视图中的您选择的文件夹中,以从它创建一个预制体。
-
当我们的SelectedBomb成为预制体(场景的层次结构中为蓝色)时,您可以将其从场景中删除。
我们现在有了我们的SelectedBomb预制体。让我们在我们的DragItem.cs脚本中声明一个变量,该变量将存储在放置时实例化的预制体。我们可以通过以下步骤完成此操作:
-
从网格中选择我们的炸弹GameObject。
-
打开附加到其上的
DragItem.cs脚本,并使用以下代码添加此公共变量:public Object CreateOnDrop; -
保存脚本并返回 Unity。
-
从网格中选择我们的炸弹GameObject,并将项目视图中的SelectedBomb预制体拖放到其Drag Item的创建时放置字段中。
现在让我们以以下方式为我们的时间能力做同样的事情:
-
在项目视图中选择我们的SelectedBomb预制体,并执行以下步骤:
-
使用Ctrl + D复制它。
-
将新的副本预制体重命名为
SelectedTime。
-
-
选择其标签子 GameObject,并将其文本更改为
[AAFFFF]Time。 -
在场景的层次结构中从网格中选择我们的时间GameObject。
-
从项目视图中的Drag Item的创建时放置字段中拖动我们的SelectedTime预制体。
我们现在可以向DropSurface.cs脚本添加一个OnDrop()方法来以以下方式处理放置的对象:
-
从Powers中选择我们的SurfaceGameObject。
-
打开其附加的
DropSurface.cs脚本。
OnDrop()事件有一个参数:dropped GameObject。让我们将此方法添加到我们的脚本中,以使用以下代码片段处理拖放操作:
//Called when an object is dropped on DropSurface
public void OnDrop(GameObject dropped)
{
//Get the DragItem from the dropped object
DragItem dragItem = dropped.GetComponent<DragItem>();
//If it has none, don't go further
if(dragItem == null) return;
//Instantiate the defined CreateOnDrop Object
GameObject newPower = NGUITools.AddChild(this.gameObject, dragItem.CreateOnDrop as GameObject);
//Destroy the dropped Object
Destroy(dropped);
}
保存脚本并点击播放按钮。当您将能力拖放到SurfaceGameObject 上时,没有任何操作!为什么?
这是因为 OnDrop() 事件依赖于从 Camera 发出的射线投射,并且在拖拽释放的瞬间,我们拖拽的能量的 Box Collider 组件挡在了鼠标光标和 Surface 游戏对象之间。
我们只需在拖拽时禁用 Power 的碰撞器。我们可以通过以下方式完成:
-
从 Grid 中选择我们的 Bomb 游戏对象。
-
打开其附加的
DragItem.cs脚本。
我们将使用 OnPress() 事件来完成这个操作。OnPress() 方法将以以下方式将对象的 pressed 状态作为参数:
//Method called when the Item is Pressed or Released
void OnPress(bool pressed)
{
//Invert the collider's state
collider.enabled = !pressed;
}
保存脚本并点击播放。你现在可以在表面上拖拽和释放能量了!
处理无效的释放
现在,让我们确保如果用户将能量拖拽到 Surface 游戏对象外部,能量会重新定位到默认位置。
要实现这一点,我们可以在 OnPress(false) 事件发生时检查摄像机的最后击中点。打开我们的 DragItem.cs 并在 collider.enabled = !pressed 之后添加以下行:
//If the Item is released
if(!pressed)
{
//Get the last hit collider
Collider col = UICamera.lastHit.collider;
//If there is no collider, or no DropSurface behind the Power
if(col == null || col.GetComponent<DropSurface>() == null)
{
//Get the grid in parent objects
UIGrid grid = NGUITools.FindInParents<UIGrid>(gameObject);
//If a grid is found, order it to Reposition now
if(grid != null) grid.Reposition();
}
}
保存并点击播放。如果你在 Surface 游戏对象以外的任何地方释放能量,我们的项目将会自动重新定位。太棒了!
一个小问题:你可以将它们两个都放在表面上,然后你就会陷入困境。让我们在下面的章节中探索解决方案。
替换当前项目
现在,我们将确保你只能有一个能量在表面上。如果你在 DropSurface 已经被占用的情况下拖拽第二个,当前的能量将被新的一个替换,并且拖拽项目组件将在 PowersContainer 游戏对象中重新出现。
我们需要知道当前哪个能量被放置在表面上,以及哪个原始拖拽项目组件必须在 PowersContainer 中的 Grid 中实例化:
-
在 Project 视图中选择我们的 SelectedBomb 预制件。
-
打开其附加的
Power.cs脚本。
此脚本将用于包含有关释放项目的信息。让我们声明一个新的 enum 来区分哪种类型的能量,以及一个 Object 变量来设置哪个预制件将被实例化以在替换时使可拖拽的能量项目重新出现:
//Declare an enum to define type of Power
public enum Type
{
None,
Time,
Bomb
}
//Declare a Type variable to choose it in Inspector
public Type type;
//Object variable to define the DragItem to recreate
public Object createOnDestroy;
现在,我们需要回到 Unity 中,在将它们分配给 createOnDestroy 变量之前,为我们的 Bomb 和 Time 可拖拽项目创建预制件:
-
从 Scene 的 Hierarchy 中的 Powers 中选择我们的 Bomb 游戏对象,并将其拖拽到你在 Project 视图中选择的任意文件夹中,以从它创建一个预制件。
-
在 Project 视图中选择我们的 SelectedBomb 预制件,并执行以下步骤:
-
将其 Power 组件的 Type 变量设置为 Bomb。
-
将我们从 Project 视图中拖拽的新 Bomb 预制件拖拽到 Power 组件的 Create On Destroy 字段。
-
我们将 SelectedBomb 的 Type 参数赋值给 Bomb,现在它已经分配了一个预制件,我们将实例化它以在替换时重新创建可拖拽的项目。
注意
重复步骤 1 和 2,将 Bomb 替换为 Time 以对我们的时间能量游戏对象和预制件执行相同的操作。
现在,我们必须编写一个系统来注册当前选中的力量类型。我们将以下方式使用GameManager.cs脚本来存储它:
-
打开我们的
GameManager.cs脚本并声明这个新的静态变量://This static variable will contain the selected power public static Power.Type SelectedPower = Power.Type.None; -
向其他脚本添加以下新的静态方法来设置我们的
SelectedPower://This static method changes the SelectedPower value public static void SetPower(Power.Type newPower) { SelectedPower = newPower; }
好的,我们现在有了注册当前选中力量的方法。现在是时候修改我们的DropSurface.cs脚本了:
-
从Powers中选择我们的Surface GameObject,并打开
DropSurface.cs脚本。 -
声明一个新的
GameObject变量来存储我们的Grid GameObject:public GameObject dragItemsContainer; -
保存脚本,在层次结构视图中从Powers中选择我们的Surface GameObject。从其DropSurface组件的Drag Items Container字段中拖动PowersContainer中的Grid GameObject。
现在,回到我们的DropSurface.cs脚本。我们将添加以下几行代码来处理用户不能在表面上放下两个力量的事实;它会替换先前的力量并重新创建其原始的Drag Item。在OnDrop()方法中,就在if(dragItem == null) return行下面,添加以下代码:
RecreateDragItem();
现在,向文件中添加一个新的RecreateDragItem()方法:
void RecreateDragItem()
{
//If there's already a Power selected
if(GameManager.SelectedPower != Power.Type.None)
{
//Get the selected power's Power.cs script
Power selectedPowerScript = transform.GetChild(0).GetComponent<Power>();
//Add the Drag Item to the grid
NGUITools.AddChild(dragItemsContainer, selectedPowerScript.createOnDestroy as GameObject);
//Destroy the currently selected Power
Destroy(selectedPowerScript.gameObject);
}
}
好的,我们现在必须通知GameManager.cs脚本选中的力量已更改。我们可以通过调用我们的SetPower()静态方法来实现。
在OnDrop()方法中,在Destroy(dropped)行之前添加以下行:
//Set the new selected power in the GameManager
GameManager.SetPower(newPower.GetComponent<Power>().type);
保存所有脚本并点击播放按钮。你现在可以在Surface GameObject 上放下第一个力量,然后放下第二个。第一个力量现在被替换,并将重新出现在可用力量容器中。
不幸的是,我们无法简单地删除力量。让我们在以下部分中纠正这一点。
删除当前项
我们希望通过点击来删除选中的力量。在我们的DropSurface.cs脚本中,添加这个新的OnClick()方法,当用户点击表面时将被调用:
void OnClick()
{
//Recreate the DragItem now
RecreateDragItem();
//Reset SelectedPower to None
GameManager.SetPower(Power.Type.None);
//Force reposition of the grid
dragItemsContainer.GetComponent<UIGrid>().Reposition();
}
现在,点击播放。你现在可以通过右键或左键点击来删除选中的力量。
使用 NGUI 的动画
NGUI 的一个优点是你可以将 Unity 的动画系统应用于任何类型的控件。还有一些 Tween 组件,允许你在一段时间内修改大多数值,例如尺寸、颜色和缩放。例如,你可以在 5 秒内将一个对象的颜色从颜色 A 变为颜色 B。
我们有一个很好的主菜单。但实际上,我们的选项是持续显示的。这并不太友好。
我们将使用动画和 tween 来隐藏我们的选项,并在用户点击选项按钮时显示它们。当选项隐藏时,我们的菜单将看起来如下所示:

但首先,让我们使力量的出现更加平滑。
平滑出现效果
让我们在我们的预制体上添加缩放 Tween,通过以下步骤使它们平滑出现:
-
在项目视图中,选择我们的SelectedBomb预制体。
-
通过导航到组件 | NGUI | Tween附加一个Scale Tween组件并执行以下步骤:
-
将其From参数设置为
{0,0,0,}。 -
将其持续时间设置为
0.2。
-
-
右键单击Tween Scale组件,然后单击复制组件。
-
选择我们的SelectedTime、Bomb和Time预制体。
-
在检查器视图中,右键单击任何现有的组件名称,然后单击粘贴组件为新组件。
现在,一旦这些小部件被创建,它们将在 0.2 秒内从 0 缩放到 1,这使得它们看起来平滑。
现在,我们可以看到我们如何使用按钮来隐藏和显示选项。
裁剪以隐藏选项
首先,我们必须隐藏我们的选项框。为此,我们将使用面板 裁剪并在需要显示它们时增加它们的宽度。让我们设置裁剪选项:
-
从MainMenu中选择我们的Window GameObject,并将其尺寸设置为
515x850。 -
选择MainMenu GameObject 并执行以下步骤:
-
在UIPanel中将它的深度设置为
-1。 -
使用Alt + Shift + N创建MainMenu的新子项。
-
将这个新子项重命名为
Container。
-
-
选择我们新的Container GameObject。
-
通过导航到组件 | NGUI | UI将其附加一个Panel组件,然后执行以下步骤:
-
将其深度设置为
0。 -
将其裁剪参数设置为Alpha 裁剪。
-
将其大小设置为
515x1080。
-
-
使用每个旁边的箭头折叠MainMenu的所有子项。
-
选择MainMenu的每个子项(除了新的Container子项),并将它们全部拖入我们新的Container GameObject 中。
好的,现在我们的选项已经隐藏了。你的层次结构应该看起来如下面的截图所示:

让我们添加一个选项按钮,它将显示或隐藏这些选项:
-
选择并复制我们的Play GameObject 从按钮,并将这个新副本重命名为
选项。 -
从按钮中选择我们新的Options GameObject 并执行以下步骤:
-
在UIAnchor中将它的侧边参数设置为底部。
-
将其在UIAnchor中的像素偏移重置为
{0,0}。 -
在Box Collider中将它的大小设置为
{140,40,0}。 -
移除其点击时加载级别组件。
-
-
从选项中选择我们的Background GameObject 并设置其尺寸为
140x40。 -
从选项中选择我们的Label GameObject:
-
将其文本更改为
选项。 -
将其溢出参数设置为收缩内容。
-
将其尺寸设置为
90x25。
-
好的,所以现在我们有一个选项按钮。接下来,我们希望它在点击时扩大我们的Window和Container的面板裁剪宽度。我们可以通过代码来做这件事,但我们将使用以下方式使用 tweens 和动画来查看它们是如何工作的:
-
在Container中选择我们的Window GameObject。
-
通过导航到组件 | NGUI | Tween,将其附加一个Tween Width组件。然后执行以下步骤:
-
将其 从 参数设置为
515。 -
将其 到 参数设置为
1300。 -
将 持续时间 设置为
0.5。 -
将 尺寸 重置为
515x850。 -
禁用 缓动宽度 组件以防止它在开始时缓动。
-
我们有一个 缓动 组件,当激活时,会调整 窗口 的 宽度。让我们使用 UIPlay 缓动 组件在点击 选项 按钮时启动它:
-
选择我们的 选项 按钮 GameObject。
-
通过导航到 组件 | NGUI | 交互,附加一个 播放缓动 组件。然后执行以下步骤:
-
将我们的 窗口 GameObject 从 容器 拖到 缓动目标 字段中。
-
将 播放 方向设置为 切换。
-
点击播放。你会看到当点击 选项 时,窗口会按需调整大小。然而,裁剪 参数没有变化。让我们使用 Unity 动画来纠正这个问题:
-
从 主菜单 中选择我们的 容器 GameObject。
-
通过导航到 窗口 | 动画 打开 动画 窗口。
-
点击红色记录按钮。
-
将动画保存为
ShowOptions.anim并执行以下步骤:-
再次输入
515作为裁剪的 X 大小 以添加一个关键帧。 -
将时间指针移动到 动画 窗口的
0:30。 -
在 UIPanel 中输入
1300作为裁剪的 X 大小 以添加一个关键帧。 -
再次点击红色记录按钮以完成。
-
-
在 动画 组件中取消选中其 自动播放 布尔值。
我们的动画已经准备好了。现在,让我们以下述方式将按钮链接到动画:
-
从 按钮 中选择我们的 选项 GameObject。
-
通过导航到 组件 | NGUI | 交互,向它附加一个 播放动画 组件。然后执行以下步骤:
-
将我们的 容器 GameObject 从 主菜单 拖到 目标 字段中。
-
对于 剪辑名称 参数,输入
ShowOptions。 -
将 播放 方向设置为 切换。
-
点击播放。当点击 选项 按钮时,我们的窗口和裁剪在两个方向上都完美地调整大小。
但你可以看到,我们的 选项 小部件直到你实际拖动主菜单才可见;这是因为动画后裁剪没有被刷新。
-
为了解决这个问题,我们可以简单地强制一个 动画结束时的拖动 选项。
-
选择我们的 主菜单 GameObject 并执行以下步骤:
-
为它创建并添加一个新的
UpdatePanel.cs脚本。 -
打开我们的新
UpdatePanel.cs脚本。
-
-
现在,将这个新的
UpdateNow()方法添加到将强制在 主菜单 上拖动 (0, 0, 0) 值的脚本中:public void UpdateNow() { //Force a drag of {0, 0, 0} to update Panel GetComponent<UIDraggablePanel>().MoveRelative(Vector3.zero); } -
保存脚本,然后执行以下步骤:
-
在 按钮 中选择我们的 选项 GameObject。
-
将我们的 主菜单 GameObject 拖到 UIPlay 动画 组件的 通知 字段中。
-
为 方法 字段选择我们新的
UpdatePanel.UpdateNow方法。
-
-
点击播放按钮。动画播放后,选项框现在会出现!
太棒了!我们已经使用了 NGUI 的 缓动 和 播放动画 组件来增强我们的 UI,使其更美观、更用户友好。
可滚动文本
让我们添加一个带有用户说明的欢迎文本框。此文本可以使用鼠标滚轮或简单的点击并拖动进行滚动。它将如下截图所示:

在开始时,它将自动滚动。现在让我们创建它:
-
选择我们的 昵称 容器 GameObject,通过按 Ctrl + D 复制它。
-
将这个新副本重命名为
Help。 -
选择此 帮助 GameObject 并执行以下步骤:
-
将我们的 标题 GameObject 拖到其 UIAnchor 的 容器 字段中。
-
在 UIAnchor 中将其 侧 参数设置为 底部。
-
在 UIAnchor 中将其 像素偏移 设置为
{0,-50}。
-
-
通过导航到 组件 | NGUI | UI 附加一个面板组件:
-
将其 深度 设置为
1。 -
将其 剪辑 参数设置为 Alpha Clip。
-
将其 剪辑大小 设置为
440x85。
-
-
通过导航到 NGUI | 附加 为其附加一个 Collider 对象,并将其 大小 设置为
{440,85,0}。 -
从 帮助 中删除 输入 GameObject。
-
从 帮助 中选择我们的 标签 GameObject 并执行以下步骤:
-
将其 字体 更改为 SciFi Font – Normal。
-
移除其 UIAnchor 组件。
-
将其文本设置为:
Welcome! [HIT RETURN KEY] [HIT RETURN KEY] You can Select one of two [AAFFFF]Powers[FFFFFF]: [AAFFAA]Bomb[FFFFFF]: Explodes all enemies at once [AAFFAA]Time[FFFFFF]: Reduces Time speed for 10 seconds
-
-
通过导航到 组件 | NGUI | 交互 为其附加一个 Tween Position 组件。然后执行以下步骤:
-
将其 From 参数设置为
{0,-50,0}。 -
将其 To 参数设置为
{0,20,0}。 -
将其 持续时间 值设置为
1.5。 -
将其 开始延迟 值设置为
3。
-
-
将其 变换 位置设置为
{0,-50,0}。 -
选择 帮助 中的 背景 GameObject 并执行以下步骤:
-
将其 尺寸 设置为
440x85。 -
将其 颜色色调 设置为
{150,255,255,255}。
-
点击播放按钮。我们现在有一个自动滚动的欢迎文本,通过在剪辑面板内部改变其 Y 坐标来实现。现在让我们启用可滚动文本的滚动轮和鼠标拖动:
-
选择我们的 帮助 GameObject。
-
通过导航到 组件 | NGUI | 交互 为其附加一个 拖动对象 组件。然后执行以下步骤:
-
将我们的 标签 GameObject 从 帮助 拖到 目标 字段。
-
将 缩放 设置为
{0,1,0}以限制垂直滚动。 -
将 滚动轮因子 值设置为
1。 -
选择 限制在面板内 布尔值。
-
点击播放。你现在可以使用左键点击并拖动或鼠标滚轮手动拖动文本标签。帮助 GameObject 上的 Box Collider 组件检测鼠标事件,而 UIDrag Object 则相应地通过改变 标签 的 Y 位置来做出反应。
我们不得不在 帮助 GameObject 中添加一个 UIPanel 组件,以便限制在剪辑边界内的移动。
本地化系统
现在我们已经有一个完整的 UI,让我们配置 本地化 系统 并添加一个弹出列表来更改 UI 的语言。
本地化文件
我们所有的本地化文本字符串都必须包含在每个语言的 .txt 文件中。为了本书的目的,我们将有英语和法语,我们需要 English.txt 文件和 French.txt 文件。
让我们立即以以下方式创建它们:
-
使用您的文件资源管理器访问项目文件夹的 Assets 文件夹,并创建一个名为
Localization的新文件夹。 -
在这个新文件夹内,创建一个名为
English.txt的新文本文件。 -
复制这个新的
English.txt文件并将其重命名为French.txt。 -
在您喜欢的 IDE 或文本编辑器中同时打开它们。
好的,现在我们的本地化文件已准备好与本地化系统一起使用。
本地化组件
我们现在可以配置本地化系统以与我们的 UI 一起工作。我们需要在场景中的 GameObject 上附加本地化组件:
-
选择我们的 GameManager GameObject,并通过导航到 组件 | NGUI | 内部 来将其附加一个 本地化 组件。
-
在 项目 视图中,导航到 Assets | 本地化,并将我们的
English.txt和French.txt文件拖入语言数组中。
起始语言 设置为 英语,我们还在数组中提供了 法语。
语言选择框
下一步是创建一个如图所示的语言选择框:

如果我们想看到我们在做什么,我们首先应该通过将 容器 中的 UIPanel 的 裁剪 参数设置为 无 来禁用 裁剪。
一旦禁用裁剪,请按照以下步骤操作:
-
从 难度 中选择 弹出 GameObject:
-
在 UIPopup List 中检查其 本地化 布尔值。
-
将其子 标签 GameObject 重命名为
CurrentDifficulty。
-
-
选择并复制 容器 中的 难度 GameObject。
-
将新副本重命名为
Language。 -
在 语言 中选择我们新的 背景 GameObject 并将其 UIAnchor 中的 像素偏移 设置为
{420, -90}。注意
有时,锚点不会自动更新。您可能需要先禁用并重新激活 语言 GameObject,以便更新标签的 UIAnchor。
-
从 语言 中选择我们的 标签 GameObject 并将其文本更改为
[AAFFFF]语言。 -
从
语言中选择我们的 弹出 GameObject 并执行以下步骤:-
将 选项 文本值更改为以下两个选项:
English French -
将其 位置 设置为 下方。
-
检查 本地化 布尔值。
-
-
通过导航到 组件 | NGUI | 交互 来附加一个 语言选择 组件。
-
将 Popup 的子 标签 重命名为
CurrentLanguage。
现在,我们可以通过将 容器 中的 UIPanel 的 裁剪 参数设置为 Alpha 裁剪 来重新激活 裁剪。裁剪 大小 已保存。
那就这样,我们的本地化系统已经到位,当弹出列表的值改变时,Language Selection.cs脚本会自动更改本地化的当前语言值。
本地化标签
我们现在准备好使用UILocalize组件和一个Key来本地化我们的第一个标签,这个Key将定义从我们的.txt文件中使用哪个字符串。执行以下步骤:
-
从标题中选择我们的标签GameObject。
-
通过导航到组件 | NGUI | UI附加一个Localize组件到它上。
-
通过输入
MainMenu设置其Key参数。 -
切换到我们的
English.txt本地化文件,并添加以下行:MainMenu = [AAFFFF]Main Menu -
切换到我们的
French.txt本地化文件,并添加以下行:MainMenu = [AAFFFF]Menu Principal
保存这两个.txt文件,并点击播放按钮。如果你访问选项并更改语言为法语,我们的窗口标题将从主菜单变为Menu Principal。这是因为UILocalize组件使用我们.txt本地化文件中=后面的字符串更新它附加的UILabel组件。如果你退出播放模式,语言将被保存,UI 将使用最后选择的语言初始化。
现在,我们必须为场景中的每个标签添加一个具有特定Key的UILocalize组件,然后在我们的两个.txt文件中为它们中的每一个添加本地化字符串。
不要害怕;它并不长,这将训练你使用本地化系统:
-
在层次结构视图中,在搜索框中输入
Label。 -
通过按Ctrl + A选择所有匹配搜索的标签GameObject,并通过导航到组件 | NGUI | UI将一个Localize组件附加到选择上。
-
从标题中选择我们的标签GameObject,并移除其第二个UILocalize组件,因为它已经有一个了!
现在,我们所有的标签GameObject 都附加了一个UILocalize组件。一个接一个地选择它们,并根据它们在UILabel组件中设置的文本设置它们的Key参数。
你必须忽略弹出列表的标签;由于我们已经检查了它们的本地化布尔值,它们不需要UILocalize组件。只需为它们的选项添加具有相同名称的本地化字符串:正常、困难、英语和法语。
注意
不要忘记将相同的操作应用于我们四个不同的力量预制件中的标签:时间、炸弹、选定时间和选定炸弹。在本地化文件中,新行(回车)被\n替换。颜色工作方式相同。
当你设置完它们的Key参数后,切换到我们的English.txt文件,并添加你需要的每个键,然后加上=后面跟着相应的本地化文本。
现在,复制所有这些键声明,并将它们粘贴到我们的French.txt文件中,然后替换英语单词为法语单词或任何其他你选择的语言。
当你完成时,我们的整个 UI 将完成本地化!
摘要
在本章中,我们首先学习了如何设置可拖拽的面板。然后,我们创建并配置了一个拖拽系统,使用 UIDrag Object 组件和自定义代码来选择我们想要的能量。
记得经常使用 UIGrid 组件来自动对齐对象——当与 UIAnchor 和 UIStretch 组件明智地结合使用时,它们非常强大。
使用 Unity 的动画和 NGUI 的 Tweens 对我们来说不再是秘密——我们使用它们为这些能量创建平滑的显现效果,并将它们与裁剪结合使用,以隐藏/显示我们的选项菜单。
最后,我们创建了可滚动的文本,并学习了如何使用本地化系统来设置多种语言。
我们现在可以查看下一章中如何使用 C# 与 NGUI,并看看我们可以通过代码实现什么。
第四章:C# 与 NGUI
在本章中,我们将讨论使用 NGUI 的 C# 脚本。我们将学习如何处理事件并与它们交互。我们将使用它们来:
-
播放动画和补间
-
通过代码使用本地化文本更改标签
-
为我们的 UI 添加键盘键
-
创建通知和工具提示
我们还将看到一些 NGUI 的面向代码的组件,例如事件转发和消息发送。
事件方法
当使用 C# 与 NGUI 一起使用时,有一些方法您将在需要知道对象当前是否被悬停、按下或点击时经常使用。
如果您将脚本附加到具有碰撞器的任何对象上(例如,按钮或 3D 对象),您可以在脚本中添加以下有用的方法来捕获事件:
-
OnHover(bool state): 当对象被悬停或取消悬停时调用此方法。state布尔值给出悬停状态;如果state是true,则光标刚刚进入对象的碰撞器。如果state是false,则光标刚刚离开碰撞器的边界。 -
OnPress(bool state): 此方法与之前的OnHover()方法完全相同,只是在对象被按下时调用。它与触摸或点击一起工作。如果您需要知道用于按下对象的鼠标按钮,请使用UICamera.currentTouchID变量;如果此 int 等于 -1,则表示左键点击。如果等于 -2,则表示右键点击。最后,如果等于 -3,则表示中键点击。 -
OnClick(): 此方法与OnPress()类似,但此方法仅在点击被验证时调用,即OnPress(true)事件之后跟随OnPress(false)事件。它与鼠标点击和触摸一起工作。注意
您还可以使用
OnDoubleClick()方法,它的工作方式相同。 -
OnDrag(Vector2 delta): 在鼠标或触摸在OnPress(true)和OnPress(false)事件之间移动的每一帧调用此方法。Vector2delta参数给出对象自上一帧以来的移动。 -
OnDrop(GameObject droppedObj): 当对象被丢弃到附加此脚本的 GameObject 上时调用此方法。丢弃的 GameObject 作为droppedObj参数传递。 -
OnSelect(): 当用户点击对象时调用此方法。除非另一个对象被点击或对象被取消选择(点击空白处),否则不会再次调用。 -
OnInput(string text): 当用户在选中对象时输入文本时调用此方法。text参数给出输入的文本。 -
OnTooltip(bool state): 当光标在对象上超过由 UICamera 的 Tooltip Delay 检查器参数定义的持续时间时调用此方法。如果 UICamera 的 Sticky Tooltip 布尔值被选中,则工具提示将保持可见,直到光标移出 Collider 的边界,否则工具提示将在光标移动时消失。 -
OnScroll(float delta): 当鼠标的滚轮在对象上悬停时移动时,此方法会被调用——delta 参数给出了滚动量及其方向。 -
OnKey(KeyCode key): 当用户在对象被选中时点击一个键,此方法会被调用。按下的键存储在key参数中。
注意
如果您将脚本附加到 3D 对象以捕获这些事件,请确保它位于UICamera的Event Mask中包含的层。
创建工具提示
现在我们使用OnTooltip()事件来显示我们的工具提示,如下截图所示:

我们还将确保它使用与 NGUI 集成的集成方法进行本地化。
工具提示引用
首先,我们将创建一个在需要时显示的工具提示。以下是执行此操作的步骤:
-
使用Ctrl + D选择并复制我们的Help GameObject。然后执行以下步骤:
-
将此新副本重命名为
Tooltip。 -
在UIPanel中将Depth设置为
4。 -
将UIPanel中的Clipping设置为None。
-
移除其Box Collider组件。
-
移除其UIDrag
Object组件。 -
移除其UIAnchor组件。
-
将其Transform位置重置为{
0,0,0}。
-
-
选择我们的新Background,它是Tooltip的子 GameObject。然后执行以下步骤:
-
将其Depth值设置为
0。 -
将其Pivot参数设置为Top Left(左箭头 + 上箭头)。
-
将其Transform位置重置为{
0,0,0}。 -
将其Dimensions设置为
200x50。
-
-
选择Label,它是Tooltip的子 GameObject。然后执行以下步骤:
-
将其Depth设置为
1。 -
将其文本更改为
This is a Tooltip。 -
将其Overflow参数更改为Resize Height。
-
移除其Tween Position组件。
-
移除其UILocalize组件。
-
将其Pivot参数设置为Top Left(左箭头 + 上箭头)。
-
将其Transform位置设置为{
15,-15,0}。 -
将其Dimensions参数设置为
200x20。
-
-
选择我们的Tooltip GameObject。
-
通过导航到Component | NGUI | UI并为所选对象附加一个Tooltip,执行以下步骤:
-
将我们的Label GameObject 从Tooltip拖到其Text字段。
-
将我们的Window GameObject 从Tooltip拖到其Background字段。
-
好的。我们的工具提示已经准备好显示。我们已将Tooltip下的Label的Pivot参数设置为Top left,位置为{15, -15, 0},这将强制在文本和背景精灵之间产生一个边距。
Overflow参数将允许文本仅按高度调整大小,这将使我们的工具提示即使在长工具提示的情况下也能保持一致——Background精灵将自动调整大小以适应Label GameObject。
显示工具提示
我们现在必须显示工具提示。为了做到这一点,我们只需要使用OnTooltip()事件,在其中我们将创建一个新的带有本地化文本的工具提示。
在 项目 视图中,选择我们的 Time 和 Bomb 预制件,并为其创建并添加一个新的 TooltipManager.cs C# 脚本。
你现在可以打开这个新的 TooltipManager.cs 脚本,并声明以下枚举,它将定义必须显示哪种类型的工具提示:
//Enum to define which type of tooltip must be shown
public enum Type
{
Bomb,
Time
}
//Declare the Type enum variable
public Type type;
好的,现在添加以下 OnTooltip() 方法,它将创建一个带有本地化文本的工具提示,其类型取决于当前类型:
//When a Tooltip event is triggered on this object
void OnTooltip(bool state)
{
//If state is true, create a new Tooltip depending on the type
if(state)
UITooltip.ShowText(Localization.instance.Get(type.ToString() + "Tooltip"));
//If state is false, hide tooltip by setting an empty string
else
UITooltip.ShowText("");
}
保存脚本。如您所见,我们使用了一个有用的 Localization.instance.Get(string key) 方法,它返回与传递的 key 参数相对应的本地化文本。您现在可以通过代码随时将标签更改为本地化文本!
注意
要使用 Localization.instance.Get(string key),您的标签不得附加 UILocalize 组件;否则,UILocalize 的值将覆盖您分配给标签的任何内容。
好的,我们已经添加了代码来显示带有本地化文本的工具提示。现在我们必须使用以下代码将这些本地化字符串添加到 English.txt 文件中:
BombTooltip = Explodes all\nenemies at once
TimeTooltip = Slows Time\nfor 10 seconds
类似地,在 French.txt 文件中添加以下行:
BombTooltip = Détruit tous les ennemis d'un coup
TimeTooltip = Ralentit le temps pour 10 secondes
保存这些文件,然后返回 Unity,通过执行以下步骤来分配 TooltipManager 类型变量的值:
-
在 项目 视图中,选择我们的 Bomb 预制件,并在 TooltipManager 中将其 Type 字段设置为
Bomb。 -
在 项目 视图中,选择我们的 Time 预制件,并在 TooltipManager 中将其 Type 字段设置为 Time。
点击播放按钮。当您将光标放在 可用能力 槽中的 Bomb 或 Time 能力上时,我们的本地化工具提示就会出现!我实际上觉得延迟太长了。让我们纠正一下。
-
从 UI Root (2D) 中选择我们的 Camera GameObject,并在 UICamera 中将其 Tooltip Delay 值设置为
0.3。
这样更好——我们的本地化工具提示在悬停 0.3 秒后出现。
缓动方法
你可以通过在您最喜欢的 IDE 中的任何方法中简单地输入 Tween 来查看所有可用的缓动方法。您将看到由于自动完成而显示的 Tween 类列表,如下截图所示:

这些类的一个优点是它们只需一行即可工作,并且不需要每帧执行;您只需调用它们的 Begin() 方法即可!
在这里,我们将对小部件应用缓动,因为我们场景中只有这些。但请注意,它以完全相同的方式与其他 GameObjects 一起工作,因为 NGUI 小部件是 GameObjects。
主菜单出现
让我们使用 TweenPosition 类使我们的主菜单在启动时从屏幕顶部出现。我们首先将使用只有一行的一个简单的缓动,然后我们将添加一个带有延迟的缓动效果,使其看起来更美观。
简单缓动
我们可以在 主菜单 中的 Container GameObject 上添加一个 Tween Position 组件,但我们需要看看如何在代码中创建 Tween。以下是我们这样做的方式:
-
从 项目 视图中选择我们的 Container GameObject,并在其中创建并添加一个新的
AppearFromAbove.csC# 脚本。 -
现在打开这个新的
AppearFromAbove.cs脚本并编辑Start()方法,使其首先将Container中的position值设置为高于屏幕高度。然后在一秒内将其 Tween 回{0, 0, 0},如下所示:void Start () { //First, set the Menu's Y position to be out of screen: this.transform.localPosition = new Vector3(0,1080,0); //Start a TweenPosition of 1 second towards {0,0,0}: TweenPosition.Begin(this.gameObject, 1, Vector3.zero); }
点击播放按钮。现在我们的主菜单只用了两行代码就从屏幕顶部降下来!
平滑 Tween
我们创建了一个简单的 Tweens,但你也可以配置你的 Tweens 以添加平滑方法和延迟,例如。
让我们尝试通过将Start()方法的代码替换为以下代码来尝试它:
void Start ()
{
//First, set the Menu's Y position to be out of screen
this.transform.localPosition = new Vector3(0, 1080, 0);
//Start a TweenPosition of 1.5 second towards {0,0,0}
TweenPosition tween = TweenPosition.Begin(this.gameObject, 1.5f, Vector3.zero);
//Add a delay to our Tween
tween.delay = 1f;
//Add an easing in and out method to our Tween
tween.method = UITweener.Method.EaseInOut;
}
点击播放按钮。我们已经为我们的 Tweens 添加了一个不错的EaseInOut方法。菜单的垂直移动现在更平滑,所有这些都是通过代码添加的。以下是可以添加到 Tweens 以产生效果的不同的 Tweens 方法列表:
-
Linear: 这将创建一个简单的线性 tween—没有平滑 -
EaseIn: 这将在动画开始时使 tween 平滑 -
EaseOut: 这将在动画结束时使 tween 平滑 -
EaseInOut: 这将在动画开始和结束时使 tween 平滑 -
BounceIn: 这将在动画开始时产生弹跳效果 -
BounceOut: 这将在动画结束时产生弹跳效果
现在你已经知道了如何使用TweenPosition类,你就可以使用其他 Tweens,例如TweenScale、TweenRotation、TweenColor或任何其他可用的 Tweens,因为它们的工作方式相同!
使用按键进行导航
我们创建的 UI 可以通过鼠标操作。我们可以轻松地添加按键导航以支持键盘和控制器。存在一个UIButton Keys组件用于此目的。你必须将其添加到任何你希望可以通过按键访问的 UI 元素(默认的检查器窗口如下):

让我们尝试使用我们的播放、退出和选项按钮:
-
选择我们的退出、选项和播放GameObject。
-
通过导航到组件 | NGUI | 交互,为它们附加一个按钮按键组件。
-
将出现一个弹出窗口,如下面的截图所示,询问你是否要替换或添加碰撞体:
![使用按键进行导航]()
-
这是因为它们已经有一个盒子碰撞体组件。点击替换。
-
选择播放按钮并执行以下步骤:
-
检查其开始选中布尔值。
-
将我们的退出按钮拖入左侧选中字段。
-
将我们的退出按钮拖入右侧选中字段。
-
将我们的选项按钮拖入向下选中字段。
-
-
选择退出按钮并执行以下步骤:
-
将我们的播放按钮拖入左侧选中字段。
-
将我们的播放按钮拖入右侧选中字段。
-
将我们的选项按钮拖入向下选中字段。
-
-
选择选项按钮并执行以下步骤:
-
将我们的退出按钮拖入左侧选中字段。
-
将我们的播放按钮拖入右侧选中字段。
-
将我们的 Play 按钮拖入 Selected On Up 字段。
-
点击播放。默认情况下,我们的 Play 按钮被选中,如果您使用键盘箭头,您将能够导航到这三个按钮并通过 Return 验证。
错误通知
我们希望用户在输入字段中输入昵称并选择力量值之后才能启动游戏。
目前,用户可以无视输入和选定的力量值启动游戏。让我们通过以下截图所示的方法来纠正这个问题,防止游戏启动并通知用户:

我们将通过代码使用 TweenScale,将通知从 {0, 0, 0} 渐进缩放到 {1, 1, 1},按照以下步骤进行:
-
在 Hierarchy 窗口中选择我们的 Tooltip GameObject。
-
使用 Ctrl + D 进行复制。
-
将这个新复制的文件重命名为
Notification并执行以下步骤:-
在 UIPanel 中将 Depth 参数设置为 5。
-
移除其 UITooltip 组件。
-
将其 Transform 位置设置为
{0,-355,0}。
-
-
在
Notification中选择我们的 Label GameObject 并执行以下步骤:-
将其文本更改为
This is a Notification。 -
将 Overflow 参数设置为 Shrink Content。
-
将其 Pivot 参数设置为 Center(中间按钮 + 中间按钮)。
-
将其 Dimensions 设置为
550x80。 -
将其 Transform 位置重置为
{0,0,0}。
-
-
通过导航到 Component | NGUI | UI 为其附加一个 Localize 组件。
-
在
Notification中选择我们的 Background GameObject 并执行以下步骤:-
将其 Pivot 参数设置为 Center(中间按钮 + 中间按钮)。
-
将其 Transform 位置重置为
{0,0,0}。 -
将其 Dimensions 参数设置为
600x100。
-
-
选择我们的 Notification GameObject 并将其 Transform 缩放设置为
{0,0,1}。 -
创建并附加一个新的
NotificationManager.csC# 脚本到它,并打开这个新的NotificationManager.cs脚本。
好的。我们已经有了缩放比例为 {0, 0, 1} 的 Notification GameObject,现在让我们使用新的 NotificationManager.cs 脚本通过代码在 Notification GameObject 激活时启动一个 TweenScale。
我们将使用枚举来定义将显示哪种通知类型。这次,我们将使用 UILocalize 组件进行本地化文本,并通过代码更改 key 参数,而不是使用 Localization.instance.Get() 方法。
首先,在 NotificationManager.cs 脚本中声明以下变量,如下代码片段所示:
//Create an enum to define Notification Type
public enum Type
{
Nickname,
Power
}
//Declare necessary variables
public UILocalize loc;
public Type type;
//Store the Notification to access it in static methods
public static NotificationManager instance;
保存脚本。我们将在脚本中将 NotificationManager 的实例存储在场景中,以便能够轻松地从任何其他脚本访问它。
首先,让我们使用 Inspector 窗口分配我们的 Loc 变量。
选择我们的 Notification GameObject 并将 Notification 中的 Label GameObject 拖入 Loc 字段。
好的,现在回到我们的NotificationManager.cs脚本。我们将首先创建一个带有静态实例变量初始化的Awake()方法,并在游戏开始时禁用我们的Notification游戏对象,使其不可见:
void Awake()
{
//Set the static instance to this NotificationManager
instance = this;
//Deactivate Notification GameObject on awake
gameObject.SetActive(false);
}
现在我们已经编写了Awake()方法,让我们创建一个OnEnable()方法,它将声明TweenScale对象并设置UILocalize组件的相应key参数,如下所示:
void OnEnable ()
{
//Start a TweenScale of 0.5 second towards {1, 1, 1}
TweenScale tween = TweenScale.Begin(this.gameObject, 0.5f, new Vector3(1,1,1));
//Add an easing in and out method to our Tween
tween.method = UITweener.Method.EaseInOut;
//Set the Localize key to TypeName + "Notification"
loc.key = type.ToString() + "Notification";
//Force Update the UILocalize with new key
loc.Localize();
}
注意
不要忘记,如果在UILocalize组件已经激活时更改key参数,您必须调用其Localize()方法来更新它。
很好。点击播放按钮。当 Unity 运行播放模式时,激活我们的Notification游戏对象。
你可以看到我们的Notification游戏对象平滑地出现。让我们添加一个Show()方法,通过代码显示它,如下所示:
public void Show(Type notificationType, float duration)
{
//If there is no current Notification
if(!gameObject.activeInHierarchy)
{
//Set the asked Notification type
type = notificationType;
//Enable our Notification on scene
gameObject.SetActive(true);
//Start Couroutine to remove in asked duration
StartCoroutine(Remove(duration));
}
}
之前的方法通过激活其对应的游戏对象来显示我们的通知。OnEnable()方法将执行动画和本地化。
在其最后一行,Show()方法启动了Remove()协程。让我们添加以下Remove()协程,它将在给定的时间后使通知消失:
public IEnumerator Remove(float duration)
{
//Wait for the Notification display duration
yield return new WaitForSeconds(duration);
//Start the TweenScale to disappear
TweenScale.Begin(gameObject, 0.5f, new Vector3(0,0,1));
//Wait for 0.5s, the duration of the TweenScale
yield return new WaitForSeconds(0.5f);
//Deactivate the Notification GameObject
gameObject.SetActive(false);
}
太好了。现在我们可以在English.txt中添加正确的本地化字符串,如下所示:
NicknameNotification = [AAFFFF]Please Enter a [00FFAA]Nickname[AAFFFF] before you continue!
PowerNotification = [AAFFFF]Please Select a [00FFAA]Power[AAFFFF] before you continue!
我们也可以在French.txt中添加正确的字符串,如下所示:
NicknameNotification = [AAFFFF]Merci d'entrer un [00FFAA]Pseudo[AAFFFF] avant de continuer !
PowerNotification = [AAFFFF]Merci de sélectionner un [00FFAA]Power-Up[AAFFFF] avant de continuer !
如果玩家按下播放按钮而没有输入昵称,或者他没有选择一个能力,我们现在可以调用我们的Show()方法。
为了做到这一点,我们将从我们的Play按钮中移除当前的点击加载关卡组件,并将其附加一个新的LaunchValidator.cs脚本:
-
选择我们的
Play按钮游戏对象并移除其点击加载关卡组件。 -
创建并附加一个新的
LaunchValidator.csC#脚本到它上,并打开这个新的LaunchValidator.cs脚本。
在这个新脚本中,我们需要我们的昵称的UIInput组件。让我们如下声明它:
public UIInput nicknameInput;
保存脚本。现在让我们在检查器窗口中分配这个变量。然后选择我们的Play游戏对象,并将我们的Input游戏对象从昵称输入字段中的昵称拖到其启动验证器的昵称输入字段。
返回到我们的LaunchValidator.cs脚本。我们现在将添加一个OnClick()方法,在实际上载游戏之前添加昵称和能力的验证,如下面的代码片段所示:
void OnClick()
{
//If the nickname input is empty...
if(string.IsNullOrEmpty(nicknameInput.value))
{
//...Show a Nickname error notification for 2.5 sec
NotificationManager.instance.Show(NotificationManager.Type.Nickname, 2.5f);
}
//If there's a nickname but no Power is selected...
else if(GameManager.SelectedPower == Power.Type.None)
{
//...Show a Power error notification for 2.5 sec...
NotificationManager.instance.Show(NotificationManager.Type.Power, 2.5f);
}
//If there is a nickname AND a Power selected...
else
{
//... Load Game Scene
Application.LoadLevel("Game");
}
}
点击播放按钮。太完美了,我们现在有了通知,如果用户没有输入昵称或没有选择能力,将阻止游戏启动!
保存昵称
在第二章 创建小部件中,我们在昵称的自动保存键参数中输入了Nickname。它的工作原理是这样的:如果用户输入昵称并按下Return,输入的标签string值将被保存在PlayerPrefs()方法中的Nickname键。
这里的问题是:如果用户按下Return,昵称才会被保存。这是一个问题——大多数用户会在输入名字并直接选择他们的力量后不按Return——我敢肯定你也这样做过。
我们需要在用户点击播放按钮而不按Return时,将字符串保存在PlayerPrefs()方法中。
我们必须在LaunchValidator.cs脚本的OnClick()方法末尾添加一行,以便在加载游戏场景之前保存昵称的输入value。在Application.LoadLevel("Game")行之前,添加以下内容:
//Save the Nickname to PlayerPrefs before launch
PlayerPrefs.SetString("Nickname", nicknameInput.value);
现在无论用户做什么,在启动游戏之前,用户的昵称都会被保存!
发送消息
在检查器窗口中,我们之前用来在精确事件上调用方法的Notify参数通常足以发送消息。然而,你可能需要向另一个 GameObject 及其子对象发送消息。
这时,UIButton 消息组件就派上用场了。我们将使用它来在游戏实际退出之前缩小我们的MainMenuGameObject 的尺寸:
-
选择我们的退出GameObject,并执行以下步骤:
-
通过导航到组件 | NGUI | 交互,将其附加一个按钮消息组件。
-
将我们的容器GameObject 从
MainMenu拖动到其目标字段。 -
在函数名称参数中输入
CloseMenu。
-
-
在MainMenu中选择我们的容器GameObject,并打开其附加的
AppearFromAbove.cs脚本。
在此脚本中,添加一个简单的CloseMenu()方法,包含以下行:
void CloseMenu()
{
//Tween the menu's scale to zero
TweenScale.Begin(this.gameObject, 0.5f, Vector3.zero);
}
现在我们需要延迟执行Application.Quit()方法,否则我们看不到我们的Tween。我们通过以下步骤来完成:
-
选择我们的游戏管理器GameObject,并执行以下步骤:
-
打开其附加的
GameManager.cs脚本。 -
用以下行替换
ExitPressed()方法中的行://Call the exit function in 0.5s Invoke("QuitNow", 0.5f);
-
-
添加一个新的
QuitNow()方法来实际退出应用程序,如下所示:void QuitNow() { Application.Quit(); }保存脚本并点击播放按钮。当你退出应用程序时,我们的菜单将自动消失。这是因为
Invoke()方法使我们能够以延迟作为第二个参数调用函数。
让我们通过以下步骤在玩家启动游戏时也添加这个不错的缩放效果:
-
选择我们的播放按钮 GameObject,并打开其附加的
LaunchValidator.cs脚本。 -
我们将需要从MainMenu中的容器GameObject。声明如下:
public GameObject menuContainer; -
返回 Unity,并按照以下方式分配这个新的menuContainer变量:
-
选择我们的播放按钮 GameObject,并将我们的容器GameObject 从MainMenu拖动到其MenuContainer字段。
-
返回到我们的
LaunchValidator.cs脚本。我们只需将Application.LoadLevel("Game")行替换为以下行:menuContainer.SendMessage("CloseMenu"); Invoke("LaunchNow", 0.5f);
-
-
好的,现在我们可以添加一个新的
LaunchNow()方法来实际启动游戏场景,如下所示:void LaunchNow() { Application.LoadLevel(levelName); }
很好,在退出或游戏启动时,菜单会缩放出来,这使得过渡更加流畅。我们还学习了如何使用 UIButton Message 组件。
注意
我们不需要勾选 包含子项 的布尔值。但值得注意的是,你可以一次性向目标及其所有子项发送消息。
转发事件
在某些情况下,另一个组件可能很有用:UIForward Events。此组件允许将附加到它的 GameObject 的事件发送到场景中的另一个 GameObject。例如,它可以用于创建多选。让我们尝试一下,使其更清晰。执行以下步骤:
-
在 网格 中选择我们的 炸弹 GameObject。
-
通过导航到 组件 | NGUI | 交互 并将其添加到 Forward Events 组件,然后执行以下步骤:
-
将我们的 时间 GameObject 从 网格 拖动到
目标字段。 -
勾选 OnHover 布尔值。
-
勾选 OnPress 布尔值。
-
勾选 OnDrag 布尔值。
-
点击播放按钮。如果你悬停、按下并拖动 炸弹 力量,时间 力量也会以相同的方式反应!这就是事件转发的作用。
现在你已经看到它是如何工作的,你可以从我们的 炸弹 GameObject 中移除 UIForward Events 组件。
摘要
在本章中,我们使用 C# 和 NGUI 创建了本地化的工具提示和错误通知。我们学习了如何使用 Tween 类通过简化方法使主菜单平滑地出现和消失。
我们还知道如何使用键在 UI 中进行导航,并且当游戏启动时,我们的昵称现在已保存。你现在知道如何使用 NGUI 组件发送消息和转发事件,这可能在将来对你有所帮助。
是时候创建一个新的 Game.unity 场景并构建一个完整的可滚动视口,这是 第五章,构建可滚动视口 的主题。
第五章。构建可滚动的视口
在本章中,我们将创建一个新的场景,并构建一个可滚动的视口,我们可以将其中的对象拖放到视口中。我们将添加如滚动条和箭头键盘滚动等有趣的功能。
这个可滚动的视口将是上一章游戏的基础。因此,从这里开始,用户将成为玩家。想法是玩家可以在视图中滚动和拖动障碍物,这将需要几秒钟来构建。敌人将从屏幕顶部下降。如果敌人接触到障碍物,他和障碍物都将被摧毁——但我们将在第七章使用 NGUI 创建游戏中处理敌人。
场景中的障碍物越多,未来障碍物的构建过程就越长——这与障碍物的冷却时间相同。
准备游戏场景
我们需要将GameManager和Notification游戏对象作为本章的预制体。
从我们的菜单场景中,通过在项目视图中选择文件夹并将它们分别拖动到您选择的文件夹中,创建这两个预制体。
现在,让我们使用Ctrl + N创建一个新的场景,并执行以下步骤:
-
按Ctrl + S保存它,并将场景名称输入为
Game。 -
在我们的新场景中,删除主相机游戏对象。在这个场景中我们不需要它。
-
将我们的GameManager预制体从项目视图拖动到层次结构视图中。
-
通过导航到NGUI | 创建新的 UI来打开UI 工具向导。
-
添加一个名为
Game的新层。 -
在我们的UI 工具向导中,将层参数设置为这个Game层。
-
点击创建您的 UI按钮。
-
选择我们的新相机游戏对象,并将其背景颜色设置为R:
0,G:0,B:0,和A:255。注意
确保您的颜色选择器弹出窗口处于RGBA值,而不是HSVA。这可以通过在点击颜色参数时使用滑块选项旁边的按钮来完成。
-
选择我们的UI 根(2D)游戏对象,然后执行以下步骤:
-
将其缩放样式参数设置为固定大小。
-
将其手动高度设置为
1080。
-
好的,我们的场景和 UI 已经准备好了。您的UI 根(2D)脚本应该如下所示:

让我们开始创建我们的可滚动的视口。
可滚动的视口
我们将首先创建一个剪切的可拖动背景,然后添加如以下截图所示的链接滚动条:

可拖动背景
我们希望玩家能够在两个轴上滚动。这意味着我们需要一个比屏幕尺寸更大和更高的背景。对于这个游戏,我们需要一个相当大的环境来强制玩家经常滚动。让我们创建一个两倍于屏幕尺寸的环境。
执行以下步骤以创建环境:
-
选择我们的 面板 GameObject 并执行以下步骤:
-
将其重命名为
Viewport。 -
将其裁剪参数设置为 Alpha Clip。
-
将其 裁剪 大小 设置为
1920x1080。
-
-
通过导航到 组件 | NGUI | 交互 来添加一个 Draggable Panel 组件并执行以下步骤:
-
将其 拖动效果 参数设置为 动量。我们不希望玩家使用弹簧效果滚动出界。
-
将其动量量值设置为
10。超过 10,释放时背景将继续滚动过多。 -
将其缩放参数设置为
{1,1,0`} 以启用 X 和 Y 滚动。
-
-
通过导航到 组件 | NGUI | 交互 来将其附加一个 Drag Panel Contents 组件。
-
通过导航到 NGUI | 附加一个碰撞器 来将其附加一个碰撞器,并将其大小设置为
{3840,2160,1`}。
现在我们已经设置了 Draggable Panel,让我们添加一个如以下截图所示的平铺背景:

-
通过导航到 NGUI | 创建一个 Widget 打开 Widget 工具 向导。然后执行以下步骤:
-
如果 Atlas 字段设置为 None,则通过导航到 资产 | NGUI | 示例 | Atlases 来将其中的 SciFi Atlas 预制拖动到其中。
-
选择 Sprite 模板。
-
选择 Honeycomb sprite。
-
选择我们的 Viewport,然后点击 添加到 按钮。
-
-
选择新的 Sprite (Honeycomb) GameObject 然后执行以下步骤:
-
将其重命名为
Background。 -
将其 Sprite 类型 设置为 Tiled。
-
将其 颜色色调 值设置为
{0,40,40,255`}。 -
将其 深度 值设置为
0。 -
将其尺寸设置为
3840x2160。
-
点击播放按钮。就这样,我们现在有一个可滚动的视口。您可以通过在点击的同时拖动鼠标来拖动背景。
链接滚动条
让我们添加滚动条以了解我们在视口中的位置。它们必须位于一个单独的面板上,该面板在视口之上渲染,这样它们就不会随着可拖动背景而移动。执行以下步骤以添加滚动条:
-
选择我们的 锚点 GameObject。
-
使用 Alt + Shift + N 创建一个新的子项并重命名为
UI。 -
通过导航到 组件 | NGUI | UI 来添加一个 Panel 组件,并将其 深度 设置为
1以确保它可以在视口上显示。 -
通过导航到 NGUI | 创建一个 Widget 打开 Widget 工具 向导。然后执行以下步骤:
-
将 模板 字段设置为 Scrollbar。
-
将 背景 设置为 Dark sprite。
-
将 前景 设置为 Highlight sprite。
-
将 方向 设置为 水平。
-
选择我们的 UI GameObject,然后点击 添加到 按钮。
-
-
在我们的 Widget 工具 向导窗口中,将 方向 设置为 垂直。选择我们的 UI GameObject,然后点击 添加到 按钮。
我们已在场景中心创建了水平和垂直滚动条,如以下截图所示:
![链接滚动条]()
现在,我们需要将它们正确放置并调整大小以适应整个屏幕。
-
选择垂直Scroll Bar游戏对象并将其重命名为
VerticalScrollbar。 -
通过导航到NGUI | Attach将其附加一个Anchor组件并执行以下步骤:
-
将我们的Viewport游戏对象拖动到Container字段中。
-
将其Side参数设置为TopRight。
-
将其Pixel Offset设置为{
-11,0}。
-
-
从VerticalScrollbar中选择我们的Background游戏对象。然后执行以下步骤:
-
将其Color Tint设置为{
130,255,245,110}。 -
将Box Collider的Center坐标设置为{
0,-540,0}。 -
将Box Collider的Size设置为{
22,1080,0}。
-
-
通过导航到Component | NGUI | UI将其附加一个Stretch组件。
-
将其Style参数设置为Vertical。
-
将其Relative Size值设置为{
1,0.983},以便在屏幕底部为水平滚动条留出空间。
-
-
从VerticalScrollbar中选择Foreground游戏对象,并将其Color Tint设置为{
0,255,128,255}。我们的垂直滚动条已配置。让我们为水平滚动条做同样的设置。
-
选择水平Scroll Bar游戏对象,并将其重命名为
HorizontalScrollbar。 -
通过导航到NGUI | Attach将其附加一个Anchor组件。然后执行以下步骤:
-
将我们的Viewport游戏对象拖动到Container字段中。
-
将其Side参数设置为BottomLeft。
-
将其Pixel Offset设置为{
0,11}。
-
-
从HorizontalScrollbar中选择我们的Background游戏对象并执行这些步骤:
-
将其Color Tint设置为{
130,255,245,110}。 -
将Box Collider的Center坐标设置为{
960,0,0}。 -
将Box Collider的Size设置为{
1920,22,0}。
-
-
通过导航到Component | NGUI | UI将其附加一个Stretch组件,并将其Style参数设置为Horizontal。
-
从HorizontalScrollbar中选择Foreground游戏对象,并将其Color Tint设置为{
0,255,128,255}。
好的。我们的水平和垂直滚动条都已设置。现在,我们需要通过以下步骤将它们分配给可滚动的视口:
-
选择我们的Viewport游戏对象。
-
从UI拖动我们的HorizontalScrollbar游戏对象到UIDraggable Panel中的Horizontal Scroll Bar字段。
-
将我们的VerticalScrollbar游戏对象从UI拖动到UIDraggable Panel中的Vertical Scroll Bar字段。
-
将Show Scroll Bars参数更改为Always。
点击播放按钮。就这样。我们的滚动条可以用来滚动,并且在我们滚动时指示我们在视口中的位置。您的层次结构应如图所示:

现在,让我们添加键盘滚动。
键盘滚动
对于这款游戏,使用键盘滚动很重要。为了做到这一点,我们将创建一个自定义脚本,该脚本将根据按下的键强制我们的滚动条移动。选择我们的Viewport游戏对象,并将其新的KeyboardScroll.cs脚本附加到它上。打开这个新脚本,并声明所需的变量和Awake()方法:
//We need the Scrollbars for keyboard scroll
UIScrollBar hScrollbar;
UIScrollBar vScrollbar;
public float keyboardSensitivity = 1;
void Awake()
{
//Assign both scrollbars on Awake
hScrollbar = GetComponent<UIDraggablePanel>().horizontalScrollBar;
vScrollbar = GetComponent<UIDraggablePanel>().verticalScrollBar;
}
好的,我们的两个滚动条都在Awake()中,并且有一个用于灵敏度的浮点值。
现在,让我们在每个帧中检查水平和垂直输入轴,并相应地更改滚动条的值:
void Update()
{
//Get keyboard input axes values
Vector2 keyDelta = Vector2.zero;
keyDelta.Set(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
//If no keyboard arrow is pressed, leave
if(keyDelta == Vector2.zero) return;
//Make it framerate independent and multiply by sensitivity
keyDelta *= Time.deltaTime * keyboardSensitivity;
//Scroll by adjusting scrollbars' values
hScrollbar.value += keyDelta.x;
vScrollbar.value -= keyDelta.y;
}
保存脚本并点击播放按钮。现在您可以使用键盘箭头滚动。您还可以根据需要调整检查器窗口中的灵敏度参数。
现在,是时候创建我们可以将其拖放到我们的Viewport游戏对象中的可拖动屏障了。
创建可拖动的屏障
是时候创建我们的可拖动屏障了。玩家将能够将BarrierObject预设拖放到Viewport游戏对象中。这个BarrierObject预设将如下面的截图所示:

BarrierObject 预设
首先,我们需要创建我们的BarrierObject预设的持有者,它将包含可拖动的对象:
-
选择我们的UI游戏对象。
-
使用Alt + Shift + N创建一个新的子对象,并将其重命名为
Barrier。 -
通过导航到NGUI | 打开并执行给定的步骤来打开Widget Tool向导:
-
将模板参数的精灵设置为Sprite。
-
将Sprite字段的精灵选择为Dark。
-
在我们的Barrier游戏对象被选中时,点击添加到按钮。
-
-
选择我们新屏障的Sprite (Dark)游戏对象并执行以下步骤:
-
将其重命名为
Background。 -
将其精灵类型设置为Sliced。
-
将其颜色色调设置为
{0,250,250,170}。 -
将其深度值设置为
0。 -
将其尺寸设置为
200x200。
-
-
选择我们的Barrier游戏对象。
-
通过导航到NGUI | 附加一个碰撞器来将其附加一个碰撞器,并执行以下步骤:
-
将Box Collider中的中心坐标设置为
{0,0,0}。 -
将Box Collider的大小参数设置为
{200,200,1}。
-
-
通过导航到NGUI | 附加来将其锚点附加到它上。
-
将我们的Viewport游戏对象拖动到其容器字段中。
-
将其侧面参数设置为TopLeft。
-
将其像素偏移设置为
{100,-100}。
-
好的,我们的BarrierObject持有者的背景现在在左上角,如下面的截图所示:

让我们创建实际的BarrierObject预设,它将是一个自定义按钮:
-
选择我们的Barrier游戏对象。
-
通过导航到NGUI | 创建一个 Widget打开Widget tool向导并执行以下步骤:
-
通过导航到资产 | NGUI | 示例 | 图集 | SciFi将SciFi Font – Normal预设拖动到Widget Tool向导的字体字段中。
-
为 Template 字段选择 Button。
-
为 Background 字段选择 Highlight 精灵。
-
在选择我们的 Barrier 游戏对象后,点击 Add To 按钮。
-
-
从 Barrier 中选择我们的新 Button 游戏对象。
-
将其重命名为
BarrierObject。 -
将其 Center 坐标 Box Collider 设置为
{0,0,0}。 -
将 Box Collider 的 Size 设置为
{160,160,0}。
-
-
将 BarrierObject 中的 Background 游戏对象拖动到 UIButton 的 Target 字段中。然后执行以下步骤:
-
将其 Normal Color 设置为
{125,255,155,130}。 -
将其 Hover Color 设置为
{100,255,60,255}。 -
将其 Pressed Color 设置为
{20,255,0,160}。 -
将其 Disabled Color 设置为
{115,115,155,255}。
-
-
从 BarrierObject 中选择 Background 游戏对象并执行以下步骤:
-
将其 Depth 值设置为
1。 -
将其 Dimensions 设置为
160x160。
-
-
从 BarrierObject 中选择 Label 游戏对象并执行给定的步骤:
-
将其文本设置为
[99FF99]Barrier。 -
将其 Depth 设置为
2。
-
好的。我们现在有了 BarrierObject 在 Barrier 持有者中。让我们通过以下步骤使其可拖动:
-
选择我们的 BarrierObject 游戏对象。
-
通过导航到 Component | NGUI | Interaction 为其附加一个 Drag Object 组件。
-
将我们的 BarrierObject 游戏对象拖动到其 Target 字段中。
-
将其 Scale 参数设置为
{1,1,0`} 以避免 Z 轴滚动。 -
将其 Drag Effect 参数设置为 None。我们希望它更精确。
-
-
为其创建并附加一个新的
BarrierObjectController.csC# 脚本。
点击播放按钮。现在,BarrierObject 预制件现在是可拖动的。现在,是时候处理在 Viewport 游戏对象上的放置操作了。
在我们继续之前,将我们的 BarrierObject 拖动到 Project 视图中你选择的文件夹中,使其成为一个预制件。
在视图中放下障碍物
为了在 Viewport 游戏对象内部放下一个障碍物,我们需要捕获 Viewport 游戏对象的 OnDrop() 事件并检查放下的是什么:
-
选择我们的 Viewport 游戏对象。
-
为其创建并附加一个新的
ViewportHolder.csC# 脚本。 -
打开这个新的
ViewportHolder.cs脚本。
在此脚本中,我们可以添加一个新的 OnDrop() 方法,当对象放在它上面时将被调用:
void OnDrop(GameObject droppedObj)
{
//Get the dropped object's BarrierObjectController
BarrierObjectController barrierObj = droppedObj.GetComponent<BarrierObjectController>();
//If it actually has one, destroy the droppedObj
if(barrierObj != null){
Destroy(droppedObj);
}
}
保存脚本并点击播放按钮。令人惊讶的是,当你将 BarrierObject 放在 Viewport 游戏对象上时,什么也没有发生!
这是因为,就像在 第三章 中一样,增强你的 UI,当 OnPress(false) 事件发生时,BarrierObject 的碰撞器被启用。这阻碍了 UICamera 的碰撞检测。
我们只需要在拖动时禁用碰撞器,并在放下时重新启用它。如果它没有放在 Viewport 游戏对象上,我们还要让它重新定位。打开我们的 BarrierObjectController.cs 脚本,并添加以下 OnPress() 方法来实现这一点:
void OnPress(bool pressed)
{
//Invert the Collider's state
collider.enabled = !pressed;
//If it has just been dropped
if(!pressed)
{
//Get the target's collider
Collider col = UICamera.lastHit.collider;
//If the target has no collider or is not the viewport
if(col == null || col.GetComponent<ViewportHolder>() == null)
//Reset its localPosition to {0,0,0}
transform.localPosition = Vector3.zero;
}
}
保存脚本并点击播放按钮。这次,当BarrierObject预制体被放置时,碰撞器被禁用。因此,它确实被放置在视口的碰撞器上并立即被销毁。
如果它被放置在其他地方(屏幕外或障碍物容器上),它将自动替换到障碍物容器的中心。让我们将这个BarrierObject作为预制体拖动到项目视图中您选择的文件夹中。
我们现在可以创建一个将在视口GameObject 上实例化的ActiveBarrier预制体。
创建一个ActiveBarrier预制体
当BarrierObject被放置在视口GameObject 上时,我们希望实例化一个将花费几秒钟构建的ActiveBarrier预制体,并使用滑块作为状态指示器,如下截图所示:

ActiveBarrier 预制体
让我们通过以下步骤创建ActiveBarrier预制体:
-
选择我们的视口GameObject。
-
使用 Alt + Shift + N 创建一个新的子对象。
-
选择这个新的子对象并将其重命名为
ActiveBarrier。 -
通过导航到NGUI | 创建小部件打开小部件工具向导并执行以下步骤:
-
选择进度条作为模板字段。
-
将暗精灵设置为空。
-
将高亮精灵设置为全。
-
在ActiveBarrier GameObject 被选中时,点击添加到按钮。
如下截图所示,进度条已作为ActiveBarrier GameObject 的子对象创建:
-

它看起来什么都没有。让我们通过以下步骤配置它,使其看起来像ActiveBarrier预制体:
-
从进度条中选择我们的新背景GameObject 并执行以下步骤:
-
取消选中其填充中心布尔值以仅保留边缘。
-
将其颜色色调设置为
{100,200,100,255`}。 -
将其深度设置为
1以确保它可以在视口背景之上渲染。 -
将其尺寸设置为
160x160。
-
-
从进度条中选择我们的前景GameObject 并执行以下步骤:
-
将其颜色色调设置为
{75,190,95,255`}。 -
将其深度值设置为
2。 -
将其尺寸设置为
160x160。
-
-
从ActiveBarrier中选择我们的进度条并执行以下步骤:
-
将其重命名为
Slider。 -
将其变换位置设置为
{-80,0,0`} 以使其居中。 -
将UISlider值设置为
0以确保它从开始时就是空的。
-
-
选择我们的ActiveBarrier GameObject。
-
通过导航到NGUI | 附加将其附加到一个碰撞器上,并设置其大小为
{160,160,1`}。
ActiveBarrier GameObject 的滑块已准备就绪。如果您在运行时点击播放按钮并在检查器视图中更改滑块值,您将看到ActiveBarrier预制体正在构建。
让我们添加一个标签来显示ActiveBarrier的状态:要么是Building,要么是Built。
-
在BarrierObject中复制我们的Label游戏对象并执行以下步骤:
-
将它拖动到我们的ActiveBarrier游戏对象内部。
-
重置其Transform Position为
{0,0,0}。 -
将其Depth设置为
3。 -
通过导航到Component | NGUI | UI为其添加一个本地化组件。
-
将UILocalize的键设置为
BuildingBarrier。
-
-
将你的选择文件夹中的ActiveBarrier拖动到Project视图以使其成为预制件。
-
从场景中删除ActiveBarrier实例。
好的,我们的ActiveBarrier预制件已经准备好了。现在,在English.txt中添加以下本地化字符串:
//Game
Barrier = [99FF99]Barrier
BuildingBarrier = [FF6666]Building\nBarrier...
Wait = Wait
还需要在French.txt中添加以下本地化字符串:
//Game
Barrier = [99FF99]Barrière
BuildingBarrier = [FF6666]Construction\nBarrière...
Wait = Attendez
现在,我们的ActiveBarrier预制件已经设置好了。
实例化 ActiveBarrier 预制件
现在我们有了预制件,当BarrierObject预制件被拖放到Viewport游戏对象内部时,我们需要实例化它。
打开我们的ViewportHolder.cs脚本并声明必要的变量:
//We need our two barriers Prefabs
public Object barrierObjectPrefab;
public Object activeBarrierPrefab;
//We need the BarrierObject container
public GameObject barrierContainer;
保存脚本。让我们回到场景,并在Inspector视图中分配这些变量:
-
选择Viewport游戏对象。
-
将BarrierObject预制件从Project视图拖动到Viewport Holder中的BarrierObject预制件字段。
-
将ActiveBarrier预制件从Project视图拖动到Viewport Holder中的ActiveBarrier预制件字段。
-
将Barrier游戏对象从Hierarchy视图拖动到Viewport Holder中的Barrier Container字段。
必要的变量已经分配。回到我们的ViewportHolder.cs脚本,并在Destroy(droppedObj)之后添加以下两行来调用适当的方法:
RecreateBarrierObject();
CreateActiveBarrier(droppedObj.transform);
现在,我们可以添加这两个方法来重新创建我们的BarrierObject预制件。我们还可以将一个ActiveBarrier预制件添加到Viewport游戏对象:
void RecreateBarrierObject()
{
//Add a BarrierObject to the container
Transform newBarrierTrans = NGUITools.AddChild(barrierContainer, barrierObjectPrefab as GameObject).transform;
//Reset its localPosition to {0,0,0}
newBarrierTrans.localPosition = Vector3.zero;
}
void CreateActiveBarrier(Transform barrierObjectTrans)
{
//Add an ActiveBarrier to the Viewport
Transform newActiveBarrierTrans = NGUITools.AddChild(gameObject, activeBarrierPrefab as GameObject).transform;
//Set position to the droppedObject's position
newActiveBarrierTrans.position = barrierObjectTrans.position;
}
点击播放按钮。当你将BarrierObject预制件拖动到Viewport游戏对象上时,它将创建我们的ActiveBarrier预制件;并且重新创建一个BarrierObject预制件以便可以拖动另一个。
障碍物构建过程
目前,我们掉落的ActiveBarrier实例保持为空且永远不会构建。让我们让它们以场景中障碍物数量的速度填充:
-
在Project视图中选择我们的ActiveBarrier预制件。
-
为它创建并添加一个
ActiveBarrierController.cs脚本。
打开这个新的ActiveBarrierController.cs脚本,并添加必要的变量和Awake()方法来初始化它们:
//We will need the Slider and the Label's UILocalize
private UISlider slider;
private UILocalize loc;
void Awake()
{
//Get necessary components at Awake()
slider = GetComponentInChildren<UISlider>();
loc = GetComponentInChildren<UILocalize>();
}
现在我们已经初始化了必要的变量,让我们添加一个协程,它将随着时间的推移增加UISlider值,速率取决于给定的buildTime:
public IEnumerator Build(float buildTime)
{
while(slider.value < 1) {
slider.value += (Time.deltaTime / buildTime);
yield return null;
}
//When slider value is > 1
BuildFinished();
}
好的。现在让我们添加BuildFinished()方法,将Slider值设置为1(如果这个值更高),并更改UILocalize键:
private void BuildFinished()
{
//Make sure it's at 1
slider.value = 1;
//Set the key to "normal" barrier and update Localization
loc.key = "Barrier";
loc.Localize();
}
好的。我们只需要编辑ViewportHolder.cs脚本,添加一个barrierCount变量,并从ActiveBarrier开始新的Build()协程。
打开ViewportHolder.cs脚本,并在我们的barrierContainer之后声明一个新的int:
public int barrierCount = 0;
现在,让我们添加这两行简单的代码来更新barrierCount变量,并在我们新的ActiveBarrier预制体上启动Build()协程:
//Update barrierCount
barrierCount++;
//Start the Build Coroutine with the correct buildTime
StartCoroutine(newActiveBarrierTrans.GetComponent<ActiveBarrierController>().Build(barrierCount *2));
点击播放按钮。现在,我们的ActiveBarrier预制体将根据场景中ActiveBarriers的数量自行构建!
将事件转发到视口
你可能已经注意到,如果你点击ActiveBarrier预制体,就无法滚动。这是因为它捕获了事件而不是我们的视口。
让我们简单地将其事件转发到视口:
-
在项目视图中选择我们的ActiveBarrier预制体。
-
通过导航到组件 | NGUI | 交互,将其附加一个转发事件组件,并执行以下步骤:
-
检查其On Press布尔值。
-
检查其On Drag布尔值。
-
-
打开附加到其上的
ActiveBarrierController.cs脚本。
我们需要在创建UIForward Event组件时分配目标变量。为此,添加一个新的Start()方法,如下所示:
void Start()
{
//Set the UIForwardEvents' target to the viewport
GetComponent<UIForwardEvents>().target = transform.parent.gameObject;
}
现在我们可以随意滚动。我们缺少一些东西:一个依赖于活动障碍物数量的冷却时间。
障碍物对象冷却
我们将实现一个冷却系统,该系统将根据以下截图所示禁用BarrierObject按钮:

然后,我们将通过缓动使障碍物的出现更加平滑。
冷却实现
为了实现所需的冷却,我们需要打开BarrierObjectController.cs脚本,并在Awake()中添加以下两个必要的变量及其初始化:
//We will need the Button and the Label
private UIButton button;
private UILabel label;
void Awake()
{
//Get necessary components at Awake
button = GetComponentInChildren<UIButton>();
label = GetComponentInChildren<UILabel>();
}
现在我们有了按钮和标签,我们可以添加一个Cooldown()协程,该协程将禁用按钮并更新标签以显示剩余时间给玩家:
public IEnumerator Cooldown(int cooldown)
{
//Deactivate the Barrier button and update Color to Disable
button.isEnabled = false;
button.UpdateColor(false, true);
while(cooldown > 0)
{
//Update Label with localized text each second
label.text = Localization.instance.Get("Wait") + " " + cooldown.ToString() + "s";
cooldown -= 1;
//Wait for a second, then return to start of While
yield return new WaitForSeconds(1);
}
//If cooldown <= 0
CooldownFinished();
}
之前的协程更新了标签并减少了冷却时间。我们现在可以添加CooldownFinished()方法,该方法将重新激活按钮并重置标签:
void CooldownFinished()
{
//Reset the Label's Text to "normal" Barrier
label.text = Localization.instance.Get("Barrier");
//Reactivate the Barrier button and update Color to Normal
button.isEnabled = true;
button.UpdateColor(true, true);
}
太好了,一切准备就绪,我们的冷却。我们只需要在创建新的BarrierObject预制体时启动Cooldown()协程。
为了做到这一点,让我们回到我们的ViewportHolder.cs脚本,并在RecreateBarrierObject()方法的末尾添加以下行:
//Start the new BarrierObject's Cooldown Coroutine
StartCoroutine(newBarrierTrans.GetComponent<BarrierObjectController>().Cooldown((barrierCount +1) *3));
完美。在这里,我们需要将barrierCount +1作为参数传递,因为在这个阶段它还没有更新(它在CreateActiveBarrier()方法中增加)。
点击播放按钮。当你将BarrierObject预制体拖放到视口游戏对象上时,你只能在冷却完成时才能放下另一个。障碍物越多,冷却时间越长。
障碍物对象平滑出现
让我们添加一个 TweenScale 来使障碍物的可用性对玩家更加明显。回到我们的 BarrierObjectController.cs 脚本,并在 CooldownFinished() 方法的最后添加以下两行代码:
//Set its scale to {0,0,0}
transform.localScale = Vector3.zero;
//Tween it back to make it appear smoothly
TweenScale.Begin(gameObject, 0.3f, new Vector3(1,1,1));
这样更好。现在,BarrierObject 的动画出现吸引了玩家的注意。但是,嘿,我们在上一章中创建了一个通知。让我们重用它来让它更加明显!
障碍物可用提示
让我们使用之前的工作在游戏中设置通知:
-
将我们的 Notification 预制体拖入我们的 UI GameObject 中。
-
在 Hierarchy 视图中选择新的 Notification GameObject,然后执行以下步骤:
-
将其 Layer 改为 Game(在 Inspector 视图的右上角)。
-
将会出现一个弹出窗口。点击 是,并更改子项。
-
打开与之相连的
NotificationManager.cs脚本。
-
首先,我们需要添加一个新的通知类型。这是通过在我们的类型 enum 中添加第三行来完成的:
BarrierAvailable
现在,将以下本地化字符串添加到 English.txt:
BarrierAvailableNotification = New [99FF99]Barrier[FFFFFF] Available!
此外,将以下本地化字符串添加到 French.txt:
BarrierAvailableNotification = Nouvelle [99FF99]Barrière[FFFFFF] Disponible !
一切设置完毕。现在,回到我们的 BarrierObjectController.cs 脚本,并在 CooldownFinished() 方法的最后添加以下代码行:
//Show Notification to inform the player
NotificationManager.instance.Show(NotificationManager.Type.BarrierAvailable, 1.5f);
点击播放按钮。一旦有新的障碍物可用,就会立即显示本地化的通知。这样我们就可以确保玩家不会错过它。
摘要
在本章中,我们学习了如何使用可滚动的背景创建可滚动的视口。我们还将其与鼠标拖动、滚动条和键盘箭头链接起来。
我们使用了 UIDrag Object 组件来创建我们的拖放系统,允许我们在可滚动的视图中拖动对象。
协程帮助我们创建了障碍物的建造过程和冷却系统。使用了 UIForward Events 组件将事件转发到视图中。最后,我们在新的 Game 场景中重新使用了我们的 Notification 预制体。
现在,我们已经有了 第七章,使用 NGUI 创建游戏 的基本元素。现在,是时候发现如何使用 第六章,图集和字体自定义 来向 NGUI 添加精灵和字体了。然后我们将使用我们自己的资产来创建一个游戏!
第六章. 图集和字体自定义
在本章中,我们将学习如何创建一个新的图集并添加我们自己的资产。在本章结束时,你将知道如何处理正常、切片和瓦片精灵。
我们将使用这些新资产来添加图标到我们的能力和选定的能力。我们还将更改不同窗口的背景,并为我们的项目添加一个新的字体。
一个小练习将让你在我们进入最后一章之前,根据你的喜好自定义主菜单。首先,我们需要学习如何创建我们自己的游戏图集。
图集预制体
使用 NGUI,图集预制体用于包含精灵和字体。它由以下部分组成:
-
包含所有精灵和字体的大型纹理文件
-
分配了此纹理文件和特定着色器的材质
图集预制体上附加了一个UIAtlas组件。它的目的是包含关于精灵在大型纹理中位置和大小的信息。
使用只包含所有精灵的一个大纹理,而不是使用多个单独的小纹理,这要高效得多。
创建新的图集
让我们创建自己的图集来存储我们的精灵和新字体。首先,打开我们的菜单场景。为了做到这一点,我们将使用图集制作器向导。
导航到NGUI | 图集制作器,或者按Alt + Shift + M来显示以下屏幕:

要创建图集,请执行以下步骤:
-
在第一个字段中,输入新图集的名称为
Game。 -
点击绿色的创建按钮。
我们的新游戏图集已经创建完成,并在图集制作器向导中被选中。目前它是空的。让我们来改变这一点。
注意
你可以创建尽可能多的图集,但请记住,同时渲染多个图集会增加绘制调用的数量。
将精灵添加到图集中
让我们在新的游戏图集中添加一些精灵。我们将添加以下三种不同类型的精灵:
-
简单精灵:正如其名所示,它只是在屏幕上显示的图像
-
切片精灵:在这种情况下,图像被分成九部分,并且可以调整大小而不会拉伸角落
-
瓦片精灵:在这种情况下,瓦片图案可以无限重复
让我们从简单精灵开始。
简单精灵
是时候创建两个精灵,炸弹和时间,它们将展示我们的能力。首先,我们需要创建这些精灵并将它们添加到我们的游戏图集中。它们将看起来如下截图所示:

注意
你可以自己创建它们,或者从goo.gl/bZu4mF下载Assets.zip文件。
如果你希望创建自己的精灵,128 x 128 的大小就足够了。你可以将它们保存为.png以支持透明度,或者作为.psd文件——当它们被导入 Unity 项目时,将转换为正确的格式。
将精灵添加到图集中
当你的炸弹和计时精灵准备好,或者从上一个链接下载后,将它们放置在你项目中的新 Assets/Textures 文件夹中。然后,执行以下步骤:
-
从 NGUI 中打开 Atlas Maker,或按 Alt + Shift + M。
-
确保我们新的 Game 图集被选中,如图所示:
![添加精灵到图集]()
-
在 项目 视图中,选择我们新的 Bomb 和 Time 纹理。
-
在 Atlas Maker 中点击 添加/更新所有 按钮。
好的。我们的两个新精灵已经添加到 Game 图集中。
可用能力图标
让我们在场景中的 Time 和 Bomb 预制件上添加图标,并执行以下步骤:
-
在 层次 视图中,通过导航到 PowersContainer | Grid | Bomb 选择 Background,然后执行以下步骤:
-
复制它。
-
将这个新副本重命名为
Icon。 -
在 UISprite 中将其 Sprite 类型 参数更改为 简单。
-
将其 深度 设置为
6以确保它可以在背景之上显示。
-
-
在 UISprite 中点击 Atlas 按钮,并在弹出的窗口中选择我们新的 Game 图集。
备注
如果新的 Game 图集不在列表中可见,从 项目 视图中将其拖到 Atlas 按钮旁边的字段中。
-
在 UISprite 中点击 Sprite 按钮,并在弹出的窗口中选择我们的 Bomb 精灵。
-
从 Bomb 中选择 Label GameObject,然后执行以下步骤:
-
删除它——图标和工具提示就足够了!
-
弹出一个窗口,询问你是否要继续,因为你将失去预制件连接。点击 继续。
-
-
从我们的 Grid 中选择 Bomb GameObject。
-
在 检查器 视图中点击 应用 按钮以更新预制件。
好的,我们的 Bomb 预制件已经更新了新的图标。让我们通过以下步骤添加 Time 预制件的图标:
-
选择我们的 Icon GameObject Bomb 并执行以下步骤:
-
复制它。
-
将这个副本拖到 Time GameObject 内。
-
将其 变换 位置重置为
{0,0,0}。 -
将其 Sprite 参数更改为我们自己的 Time 精灵。
-
将其 尺寸 更改为
75x75。
-
-
从 Time 中选择 Label GameObject,然后执行以下步骤:
-
删除它——图标和工具提示就足够了!
-
弹出一个窗口,询问你是否要继续,因为你将失去预制件连接。点击 继续。
-
-
从 Grid 中选择 Time GameObject。
-
在 检查器 视图中点击 应用 按钮以更新预制件。
好的,我们的可拖动能力预制件现在有了自己的图标。
选择能力图标
让我们也为我们的 SelectedBomb 和 SelectedTime 预制件添加图标,使它们看起来更漂亮:

执行以下步骤以实现此目的:
-
从 项目 视图,将 SelectedBomb 预制件拖到我们的 Surface GameObject。
-
在层次结构视图中,从Grid/****Bomb中选择我们的图标游戏对象,并执行给定的步骤:
-
复制它。
-
将这个副本拖动到我们新的SelectedBomb实例中。
-
将其变换位置重置为{
0,0,0}。 -
将其深度设置为
5。 -
将其尺寸更改为
120x120。
-
-
从SelectedBomb中选择标签游戏对象并删除它。
-
从Surface中选择SelectedBomb游戏对象,并执行给定的步骤:
-
在检查器视图中点击应用按钮以更新预制体。
-
从场景中删除SelectedBomb实例。
-
让我们按照相同的步骤来处理我们的SelectedTime预制体:
-
从项目视图中,将SelectedTime预制体拖动到我们的Surface游戏对象中。
-
在层次结构视图中,从Time中选择我们的图标游戏对象,并执行以下步骤:
-
复制它。
-
将这个副本拖动到我们新的SelectedTime实例中。
-
将其变换位置重置为{
0,0,0}。 -
将其深度设置为
5。 -
将其尺寸更改为
100x100。
-
-
从SelectedTime中选择标签游戏对象,并删除它。
-
从Surface中选择SelectedTime游戏对象,并执行以下步骤:
-
在检查器视图中点击应用按钮以更新预制体。
-
从场景中删除SelectedTime实例。
-
就这样。我们有了我们自己的两个用于我们力量的图标。现在,我们可以学习如何创建和配置我们自己的九切片精灵。
切片精灵
我们在这本书中一直使用暗切片精灵。让我们创建自己的。然后,我们将更改主菜单的功率选择框和背景精灵,如图所示:

向图集中添加精灵
你可以使用一个 16 x 16 的方形精灵,例如暗精灵。如果你希望有更大的角落或添加更多细节,只需使用更大的纹理尺寸。你还可以使用我Assets.zip存档中可用的 64 x 64 的Window.png文件。
当你的新窗口精灵准备好后,将其放置在Assets/Textures文件夹中,并执行以下步骤:
-
通过导航到NGUI | 图集制作器或Alt + Shift + M打开图集制作器,并确保我们的新游戏图集被选中。
-
从项目视图中的纹理中选择新的
Window.png精灵文件。 -
在图集制作器窗口中点击绿色的添加/更新全部按钮。
好的,Window.png已经添加到我们的游戏图集中,但它还没有配置。尚需。
配置切片精灵
窗口精灵已经添加到图集中,但我们还需要指示UIAtlas组件在精灵上必须进行切片的位置。让我们替换Powers的背景,并配置其切片参数。
在层次结构视图中,从Powers中选择背景游戏对象,并执行以下步骤:
-
将UISprite中的图集参数更改为我们的游戏图集。
-
将其精灵更改为我们新的Window精灵。
-
点击精灵字段旁边的编辑按钮,如图所示:

我们现在在精灵的参数窗口中。在这里,我们可以配置其尺寸、边框和填充。设置这些边框值以定义切片线:

如果您创建了您自己的精灵,之前截图中的值可能会有所不同。
注意
当您更改边框值时,在预览窗口的检查器视图底部会出现点线。
窗口精灵应该在预览窗口中切片(如图所示):

当您输入了四个值后,您可以通过点击绿色返回背景按钮返回到我们之前的位置。
现在我们已经有一个功能性的切片精灵,但我们需要调整功率标题的位置。在层次视图中,从功率中选择我们的TitleLabel GameObject,并在UI 锚点中将其像素偏移更改为{0, -18}。
太好了!我们已经配置了第一个切片精灵,并用它更改了功率选择框的背景精灵。
主菜单窗口
让我们更改我们的窗口精灵的主菜单。在层次视图中,从容器中选择我们的窗口 GameObject,并执行以下步骤:
-
将其图集更改为我们的游戏图集。
-
将其精灵更改为我们新的窗口精灵。
主菜单标题没有放置在标题栏的精确位置。让我们通过以下步骤来更改这一点。
-
从容器中选择标题GameObject,并在UI 锚点中将其像素偏移值更改为{
0,10}。 -
从标题中选择背景 GameObject,并暂时将其禁用。
-
从容器中选择背景 GameObject,然后执行以下步骤:
-
将其相对大小在UI 拉伸中更改为{
1,0.95}。 -
将其像素偏移在UI 锚点中更改为{
0,-17}。
-
太好了。看起来更好了。如果您愿意,您可以用我们新的窗口精灵更改昵称框的背景精灵。您甚至可以使用包含在Assets.zip文件中的切片精灵Button.png来更改按钮。
注意
Button.png文件也可以用于非按钮背景,例如通知或工具提示背景。试试看!
瓦片精灵
让我们添加一个瓦片精灵来为我们的游戏场景创建一个空间背景。
您可以使用Assets.zip存档中的Space.jpg文件,或者您可能创建一个代表太空中的星星的 256 x 256 瓦片精灵。将Space.jpg精灵放在Assets/Textures文件夹中,然后执行以下步骤:
-
打开我们的游戏场景。
-
通过导航到NGUI | Atlas Maker或Alt + Shift + M打开Atlas Maker。然后执行以下步骤:
-
在项目视图中,从纹理中选择我们新的
Space.jpg文件。 -
点击 Atlas Maker 窗口的 添加/更新全部 按钮。
-
好的,新的 Space.jpg 精灵已经添加到我们的 Game 图集。让我们改变 Game 场景的背景,使其看起来像我们在太空中。
-
从 视口 中选择 背景 游戏对象。
-
将其 图集 更改为我们的 Game 图集。
-
将其 精灵 更改为我们新的 空间 精灵。
-
将其 颜色色调 更改为
{140,200,200,255}。
就这样!小星星现在正在背景中平铺。现在,是时候添加字体了。
添加字体
为了优化,NGUI 使用位图而不是真型字体。我们需要使用一个名为 BMFont 的免费第三方工具将我们的 .ttf 或 .otf 字体导出为位图,该工具由 AngelCode 创建。
然后,我们需要一个包含有关每个符号在导出字体位图中位置信息的 .txt 文件。您可以从 www.angelcode.com/products/bmfont/ 下载 BMFont。免费字体可在 www.openfontlibrary.org/ 找到。
对于我们的第一个字体,我们将使用由 Daniel Johnson 创建的 Pacaya 字体——它包含在 Assets.zip 归档中。下载、安装并启动 BMFont。通过右键单击 Pacaya.otf 文件并选择 安装 来安装字体。
使用 BMFont 导出字体
一旦 BMFont 启动并且 Pacaya 字体已安装,请转到 选项 | 字体设置。现在您可以在 字体 字段中选择 Pacaya 字体。大小(px) 字段定义了导出时字体的像素大小——将其设置为 24 并点击 确定。
我们已加载 .otf 文件,并且我们可以通过左键单击或点击并拖动来可视化和选择我们想要导出的字符。使用 Ctrl + A 选择所有字符。
注意
如果您想要导出一组字符,别忘了选择空字符——它是您的空格字符。
前往 选项 | 导出 选项。在这里,您必须将 位深 设置为 32。现在,您唯一需要检查的是位图的 宽度 和 高度 值。
要查看它是否足够大,请点击 确定 并转到 选项 | 可视化。以下截图显示的窗口会出现:

红色空间代表浪费的空间。如您所见,我们有很多浪费的空间。您必须尝试设置位图大小,尽可能减少红色空间,同时确保有足够的空间容纳所有字符。
如果您的位图大小太小,无法容纳所有字符,预览 窗口的标题将显示 预览 : 1/2 而不是 预览 : 1/1,如以下截图所示。然后,您应该增加位图大小,直到它显示 预览 : 1/1。对于已选择所有字符的 Pacaya 字体,输入 256 x 128。它应该看起来像以下截图所示:

提示
为了优化,你应该保持其尺寸为 2 的幂。
一旦设置了正确的位图大小,返回到选项 | 导出选项。然后,确保预设字段设置为带 alpha 通道的白色文本,以便在R、G和B通道中具有一个值,在通道A中的符号:

在完成之后,转到选项 | 保存位图字体为…并将其命名为Pacaya。.fnt文件扩展名将被自动添加。在您选择的输出文件夹中,你应该有一个Pacaya_0.tga文件——实际的字体位图,以及一个.fnt文件。
注意
你必须有且只有一个.tga文件以及你的.fnt文件。否则,你的位图大小太小,在重新导出之前你需要将其放大。
好的,现在将这些两个文件复制到一个新的Assets/Fonts/Sources文件夹。
在 Unity 中创建字体
我们已经有了新的字体文件。我们现在必须使用这些文件为 NGUI 创建一个新的字体。
在 Unity 中,执行以下步骤:
-
通过导航到NGUI | 字体制作器或Alt + Shift + F打开字体制作器窗口。
-
在项目视图中,浏览到你的
Assets/Fonts/Sources文件夹。 -
将你的
Pacaya.fnt文件拖动到字体数据字段中的字体制作器。 -
将
Pacaya_0.tga文件拖动到字体制作器中的纹理字段。 -
在字体名称字段中输入
Pacaya。 -
点击Atlas按钮,选择我们的游戏Atlas。
注意
这意味着字体的纹理将被添加到游戏Atlas 中,当标签显示时不会产生额外的绘制调用。
-
在项目视图的
Assets/Fonts/路径下的Sources文件夹中选择任何文件——我们的字体将被添加到当前文件夹,但你需要实际选择一个文件在目标位置才能使其工作。 -
点击绿色的创建字体按钮。
在Assets/Fonts/路径下的Sources文件夹中已创建一个新的 Pacaya 预制件。这是 NGUI 需要用来显示字体的预制件。
在项目视图中选择它,并将其拖动到我们的Assets中的Fonts文件夹内。如果你找不到它,只需在项目视图的搜索栏中输入你的字体名称。
将新的字体分配给标签
现在我们已经将一个新的字体添加到我们的项目中,让我们将其分配给一个标签:
-
打开我们的菜单场景。
-
通过导航到主菜单 | 容器 | 昵称 | 输入选择我们的标签GameObject,并执行以下步骤:
-
点击UILabel中的字体按钮。
-
选择我们新的Pacaya字体。
-
注意
如果新字体没有出现在最近使用的字体中,在你的项目视图中找到它,并将其手动拖动到UILabel中的字体字段。
好的。我们已经将一个新的字体添加到我们的项目中,并将其分配给了一个标签!
自定义主菜单
我们菜单主相机的蓝色背景不太好看。让我们为相机设置一个黑色背景,并添加我们的空间平铺精灵来改善这个效果:
-
从锚点中选择我们的MainMenu GameObject,然后通过导航到NGUI | 创建 | 精灵或按Alt + Shift + S创建一个新的精灵。
-
从MainMenu中选择新的精灵GameObject,并执行以下步骤:
-
将其重命名为
Space。 -
将其图集类型设置为我们的游戏图集。
-
将其精灵设置为我们的Space平铺精灵。
-
将其精灵类型参数设置为平铺。
-
-
通过导航到组件 | NGUI | UI来为它附加一个拉伸组件:
-
将其样式参数设置为两者。
你可能会注意到,我们在 Space 精灵的每次重复之间有丑陋的线条。这仅仅是因为精灵上有一个 1 像素宽的边框。我们可以通过减少精灵的 1 像素边框值来轻松纠正这个问题。
从MainMenu中选择我们的Space GameObject,然后点击精灵字段旁边的编辑按钮,如图所示:

将四个边框参数的值设置为以下截图所示:

这样就更好了;我们的平铺精灵现在正确地平铺,没有任何图案之间的线条。
现在你已经学会了如何添加精灵和字体,我希望你改变我们的主菜单元素的背景,使其看起来更美观。你可以按照自己的意愿进行——如果你想的话,添加更多精灵,更改颜色,发挥你的想象力!
以下是一个使用窗口和按钮精灵可以实现的示例:

注意
你可能需要移动或调整 UI 元素的大小。不要忘记使用UI 锚点中的像素偏移来移动或调整大小,而不是位置。同样,对于尺寸——如果你想保持小部件像素完美,不要使用 Unity 的缩放工具。
摘要
在本章中,我们学习了如何创建一个新的图集并添加简单、切片和平铺精灵。使用这些新精灵,我们使我们的能力、选择能力和主菜单窗口比以前看起来更好。
现在,你已经知道了如何使用 BMFont 将字体导出为位图并创建一个新的字体预制件用于 NGUI。
在我们继续到最后的第七章,使用 NGUI 创建游戏之前,你应该已经更改了你的主菜单的外观,使其看起来更美观。
第七章:使用 NGUI 创建游戏
在本章的最后,我们将使用 NGUI 元素创建一个游戏,这将确保你理解它们并知道如何完美地使用它们。
一起,我们将学习如何创建以下基本游戏规则:
-
敌人从可滚动的视图中顶部掉落。
-
玩家在视图中放置障碍物——如果敌人与已建造的障碍物相撞,那么敌人和障碍物都会被摧毁。
-
一些敌人有加密的自毁代码。玩家点击敌人以破解它。当破解过程完成后,其自毁代码将显示在其上方。
-
玩家必须输入代码来摧毁敌人。
我们还将添加一个表示玩家生命值的生命条,当敌人击中屏幕底部时,它会减少。游戏将看起来如下:

敌人生成
我们希望我们的敌人从视图中背景的顶部生成。在生成时,每个敌人的 Y 值可以相同,但我们希望有一个随机的 X 值。
首先,让我们打开我们的Game场景。
创建敌人容器
我们将敌人嵌套在一个放置在我们背景顶部左角的容器中,以便将{0, 0}定位在视口的顶部左角。
首先,让我们通过以下步骤创建我们的敌人持有者:
-
选择我们的ViewportGameObject 并执行以下步骤:
-
通过按Alt + Shift + N创建一个新的子对象。
-
将这个新的子对象重命名为
Enemies。
-
-
通过导航到NGUI | Attach将其附加Anchor。然后执行以下步骤:
-
将我们的Background从视图中拖动到Container字段。
-
将其Side参数设置为TopLeft。
-
好的,我们现在有了敌人容器,我们将在这个容器中实例化我们的Enemy预制体。
创建敌人预制体
让我们创建一个Enemy预制体,它将被实例化为EnemiesGameObject 的子对象。在继续之前,你必须将包含在Assets.zip文件中的Enemy.png文件添加到Game图集中,或者你可以创建自己的 128 x 160 精灵。我们将使用Rigidbody来检测敌人和我们的障碍物之间的碰撞。
一旦将Enemy.png精灵添加到Game图集中,按照以下步骤操作:
-
选择我们的ViewportGameObject 并执行以下步骤:
-
通过按Alt + Shift + N创建一个新的子对象。
-
将这个新的子对象重命名为
Spaceship。
-
-
选择我们新的飞船GameObject。
-
通过导航到NGUI | Attach a Collider将其附加一个碰撞器,并执行以下步骤:
-
取消其Is Trigger布尔值以检测碰撞。
-
将Size设置为{
128,160,1}。
-
-
通过导航到Component | Physics将其附加一个Rigidbody组件,然后执行以下步骤:
-
取消其Use Gravity布尔值。
-
选择其Is Kinematic布尔值。
-
检查其所有约束中的冻结位置和冻结旋转布尔值,以避免任何不希望的行为。
-
-
使用我们选定的飞船GameObject,通过导航到NGUI | 创建 | 精灵创建一个新的精灵:
-
将其精灵更改为我们新的敌人精灵。
-
将其尺寸更改为
128x160。 -
将其深度设置为
1。
-
-
将我们的飞船GameObject 拖到你的
Prefabs文件夹中。 -
从场景中删除我们的飞船实例。
好的,我们现在有了我们的敌人prefab。让我们向它添加一个新的脚本,该脚本将处理敌人的初始化和移动,并执行以下步骤:
-
在项目视图中选择我们的飞船prefab。
-
向其创建并附加一个新的
EnemyController.csC# 脚本。 -
打开这个新的
EnemyController.cs脚本。
让我们创建一个新的 Initialize() 方法,该方法将设置敌人的位置在游戏外,具有随机的 X 坐标和根据传递给参数的浮点值确定的缓动持续时间:
public void Initialize(float _movementDuration)
{
//Get the Viewport's Background size
Vector2 bgSize =
transform.parent.parent.FindChild("Background").GetComponent<UISprite>().localSize;
//Get this enemy's sprite size
Vector2 spriteSize =
transform.FindChild("Sprite").GetComponent<UISprite>().localSize;
//Set its position to a random X, and Y of -(enemyHeight/2)
transform.localPosition =
new Vector3(Random.Range(spriteSize.x *0.5f, bgSize.x - (spriteSize.x *0.5f)), -(spriteSize.y *0.5f), 0);
//Tween its position towards end of background
TweenPosition.Begin(gameObject, _movementDuration,
new Vector3(transform.localPosition.x, -bgSize.y + (spriteSize.y * 0.5f), 0));
}
我们在前面的代码中使用 spriteSize.x * 0.5f 是因为我们的敌人有一个居中的枢轴,我们想要避免在背景宽度外生成它。
_movementDuration 参数用于定义敌人穿越整个背景所需的时间;它用作速度。但为了平衡速度,使用了 10 的值,这意味着敌人需要 10 秒钟才能到达背景底部。
在这个阶段,你的层次结构应该如下所示:

创建敌人生成控制器
在我们可以启动游戏之前,我们需要添加一个 EnemySpawnController.cs 脚本,该脚本将处理敌人生成速率并在需要时实例化敌人。要添加脚本,请执行以下步骤:
-
从视口中选择敌人GameObject。
-
创建并附加一个新的
EnemySpawnController.csC# 脚本。 -
打开这个新的
EnemySpawnController.cs脚本。
在这个新脚本中,我们需要添加一个 SpawnEnemy() 协程,该协程将在随机间隔被调用以实例化敌人prefab,并使用正确的位置和缓动持续时间初始化它们。首先,我们需要声明以下代码片段中所示这些变量:
//We need our Enemy Prefab for Instantiation
public Object enemyPrefab;
//Random-control variables
public int firstEnemyDelay = 1;
//Min and Max intervals between 2 spawns
public float minInterval = 4;
public float maxInterval = 15;
//Min and Max Enemy MovementTime
public float minMovementTime = 20;
public float maxMovementTIme = 50;
在前面的代码中声明的变量将被用来控制我们的随机值。你可以在检查器视图中更改它们。我们需要分配我们的 enemyPrefab 变量。
要这样做,请回到 Unity 并遵循以下步骤:
-
从视口中选择敌人GameObject。
-
将我们的飞船prefab 从项目视图拖到敌人生成控制器中的敌人预制体字段内。
好的,必要的变量已经初始化。现在,让我们回到我们的 EnemySpawnController.cs 脚本,并使用以下代码片段添加一个新的 SpawnEnemy() 协程:
//Coroutine that spawns enemies
IEnumerator SpawnEnemy()
{
//First time, set to firstEnemyDelay
float delay = firstEnemyDelay;
//Loop while the game is running
while(true){
//Wait for the correct delay
yield return new WaitForSeconds(delay);
//Create a new enemy, stock its EnemyController
EnemyController newEnemy =
NGUITools.AddChild(gameObject, enemyPrefab as GameObject).GetComponent<EnemyController>();
//Initialize it with random speed
newEnemy.Initialize(Random.Range (minMovementTime, maxMovementTIme));
//Set the new random delay
delay = Random.Range(minInterval, maxInterval);
}
}
我们的协程已经准备好了。让我们在游戏开始运行时启动它。我们可以使用Start()方法来做这件事。以下列方式在我们的SpawnEnemy()协程下方添加此方法:
void Start ()
{
//Start the Spawn Coroutine with first delay
StartCoroutine(SpawnEnemy());
}
保存脚本并点击播放按钮。在firstEnemyDelay之后,第一个敌人被生成。在第一个敌人之后,新的敌人将在随机的 X 位置、随机的间隔和随机的速度生成。
当生成几个敌人时,您的层次结构视图应如下所示:

生成的敌人会向下移动,并在视口背景的末端停止,如下面的截图所示:

前向传递事件到视口
好的,我们现在有了我们的移动敌人,但我们仍然有一个小问题。你可能已经注意到,如果你点击敌人,你将无法拖动视口。我们之前在活动障碍物预制体上也遇到了同样的问题。
我们需要通过以下步骤将UIForwardEvents组件添加到Spaceship预制体上:
-
在项目视图中,选择我们的Spaceship预制体。
-
通过导航到组件 | NGUI | 交互将其附加一个Forward Events组件。然后执行以下步骤:
-
检查其OnPress布尔值。
-
检查其OnDrag布尔值。
-
-
打开其附带的
EnemyController.cs脚本。
在EnemyController.cs脚本的Initialize()方法末尾添加以下行:
//Set the Viewport as target for UIForwardEvents
GetComponent<UIForwardEvents>().target = transform.parent.parent.gameObject;
即使点击敌人,你现在也可以平移视口。是时候处理与障碍物的碰撞了。
处理敌人碰撞
我们需要处理我们的敌人与活动障碍物之间的碰撞。由于我们的敌人预制体上附加了 Rigidbody,当它撞击活动障碍物游戏对象的碰撞器时,它将接收到OnTriggerEnter()事件。
一旦实现了与活动障碍物的碰撞,我们将添加与屏幕底部的碰撞,这将减少玩家的生命值。
与活动障碍物的碰撞
首先,我们必须默认禁用活动障碍物的碰撞器,并在以下方式中在障碍物建造时启用它:
-
在项目视图中,选择我们的活动障碍物预制体。
-
使用复选框禁用其Box Collider组件。
-
打开附在其上的
ActiveBarrierController.cs脚本。 -
我们需要一个名为
built的新布尔值,这将帮助我们了解障碍物是否完成了其建造过程。除了我们的UISlider和UILocalize变量外,声明以下内容:private bool built = false; -
现在,在
BuildFinished()方法末尾添加以下两行://Set the build value to true and activate collider built = true; collider.enabled = true; -
好的,现在碰撞器仅在建造障碍物时启用。我们可以添加一个
HitByEnemy()方法,将相关的enemy作为参数传递,以下列方式销毁障碍物和敌人:public void HitByEnemy(EnemyController enemy) { //If the barrier isn't built, don't go further if(!built) return; //Else, kill the enemy StartCoroutine(enemy.Kill()); //Kill the barrier too StartCoroutine(RemoveBarrier()); } -
在这里,我们启动了两个协程:一个用于消灭敌人,另一个用于移除障碍。现在让我们添加
RemoveBarrier()协程,使用以下代码片段:IEnumerator RemoveBarrier() { //Tween for smooth disappearance TweenScale.Begin(gameObject, 0.2f, Vector3.zero); //Notify the Viewport that a Barrier has been removed transform.parent.SendMessage("BarrierRemoved"); //Wait for end of tween, then destroy the barrier yield return new WaitForSeconds(0.2f); Destroy(gameObject); }上一段代码中的协程在销毁障碍物之前将其缩小。我们向父级(视口)发送消息,因为我们需要减少
barrierCount值。 -
让我们在
ViewportHolder.cs脚本中添加BarrierRemoved()方法。在层次结构视图中,选择我们的视口游戏对象,并打开附加到其上的ViewportHolder.cs脚本。 -
在我们的
ViewportHolder.cs脚本中,添加以下新的BarrierRemoved()方法:void BarrierRemoved() { //Decrease the barrierCount value barrierCount--; } -
一旦障碍物被销毁,
barrierCount值将立即更新。现在,让我们打开EnemyController.cs脚本并添加如以下代码片段所示的Kill()协程:public IEnumerator Kill() { //Tween for smooth disappearance TweenScale.Begin(gameObject, 0.2f, Vector3.zero); //Deactivate the collider now collider.enabled = false; //Wait end of tween, then destroy the enemy yield return new WaitForSeconds(0.2f); Destroy(gameObject); } -
太好了!我们所有的协程和方法都准备好了。现在,我们需要在发生碰撞时调用相关ActiveBarrier的
HitByEnemy()方法。 -
我们只需在
EnemyController.cs脚本中添加以下OnTriggerEnter()方法,该方法仅在碰撞对象实际上是障碍物时调用:void OnTriggerEnter(Collider other) { //Store the collided object's ActiveBarrierController ActiveBarrierController barrierController = other.GetComponent<ActiveBarrierController>(); //If it has a BarrierController, call HitByEnemy if(barrierController != null) barrierController.HitByEnemy(this); } -
保存所有脚本并点击播放按钮。
如果您在敌人的轨迹上放置一个障碍物,当它们碰撞时,两者都将被摧毁!如果构建过程尚未完成,则不会发生任何事情。
在障碍物完成其构建过程而敌人仍在其中时,将发生碰撞。太棒了!
现在玩家可以摧毁他的或她的敌人了,让我们为敌人添加一种摧毁玩家的方法。
与屏幕底部的碰撞
我们现在可以在视口背景的底部添加一个碰撞器,这将摧毁敌人并减少玩家的生命值。在我们这样做之前,让我们创建一个带有 HealthController 脚本的 Healthbar。
Healthbar
要创建此 Healthbar,我们需要Assets.zip文件中可用的Button.png文件。如果您还没有将其作为切片精灵添加到游戏图集中,请在继续之前这样做。
我们将使用进度条在 Healthbar 上创建一个,我们将添加一个HealthController.cs脚本来处理伤害和生命值的显示。执行以下步骤:
-
在层次结构视图中,选择UI游戏对象从锚点。
-
通过导航到NGUI | 创建小部件打开小部件工具窗口。然后执行以下步骤:
-
选择我们的游戏图集。
-
选择进度条模板。
-
选择空字段中的我们的按钮精灵。
-
为满字段选择我们的按钮精灵。
-
使用我们的UI游戏对象选中后,点击添加到按钮。
-
-
选择新的进度条游戏对象,并将其重命名为
Healthbar。 -
通过导航到NGUI | 附加来为它附加一个锚点。然后执行以下步骤:
-
将我们的视口游戏对象拖到容器字段中。
-
将侧边参数设置为顶部。
-
将像素偏移设置为{
-160,-30}。
-
-
从Healthbar中选择背景游戏对象并执行以下步骤:
-
将颜色色调设置为{
255,120,120,140}。 -
将尺寸设置为
320x42。 -
将精灵类型更改为切片。
-
点击精灵字段旁边的编辑按钮。
-
将所有四个边框值设置为
6作为切片参数。
-
-
从生命条中选择前景游戏对象,然后执行以下步骤:
-
将颜色色调设置为
{25,245,255,255}。 -
将尺寸设置为
320x42。 -
将精灵类型更改为切片。
-
好的,我们已经在屏幕顶部中心配置了一个健康条。我们需要向其添加一个脚本,该脚本将处理生命值并相应地修改滑块的值。以下是执行此操作的步骤:
-
在层次视图中,选择我们的生命条游戏对象。
-
向其添加一个新的
HealthController.cs脚本。 -
打开这个新的
HealthController.cs脚本。
在这个新脚本中,我们将保存对HealthController类实例的静态引用,以便其他脚本可以轻松访问其方法。首先,让我们声明必要的变量并在Awake()中初始化它们,如下面的代码所示:
//Static variable that will store this instance
public static HealthController Instance;
//We will need the attached slider and a HP value
private UISlider slider;
private float hp = 100;
void Awake()
{
//Store this instance in the Instance variable
Instance = this;
//Get the slider Component
slider = GetComponent<UISlider>();
}
好的,我们的变量现在已正确初始化。让我们创建一个Damage()方法,该方法将减少hp值并按以下方式更新滑块:
public void Damage(float dmgValue)
{
//Set new HP value with a clamp between 0 and 100
hp = Mathf.Clamp(hp - dmgValue, 0, 100);
//Update the slider to a value between 0 and 1
slider.value = hp * 0.01f;
//If hp <= 0, restart level
if(hp <= 0)
Application.LoadLevel(Application.loadedLevel);
}
太好了!Damage()方法已经准备好了。让我们创建一个EndOfScreen小部件,它将与敌人发生碰撞。
EndOfScreen 小部件
让我们创建一个EndOfScreen小部件,它将帮助检测敌人碰撞,如下所示:
-
在层次视图中,选择我们的视口游戏对象并执行以下步骤:
-
通过按 Alt + Shift + N 创建一个新的子对象。
-
将这个新子对象重命名为
EndOfScreen。
-
-
通过导航到NGUI | 附加一个碰撞器将其附加一个碰撞器,并将大小设置为
{3840,43,1}。 -
通过导航到NGUI | 附加将其附加到锚点。
-
从容器字段中的视口拖动背景游戏对象。
-
将其侧边参数设置为底部。
-
将其像素偏移设置为
{0,33}。
-
-
在检查器视图的顶部点击未标记/添加标签…按钮。
-
创建一个新的
DamageZone标签。 -
选择我们的EndOfScreen游戏对象。
-
将标签设置为DamageZone。
-
确保我们的EndOfScreen游戏对象被选中。
-
通过导航到NGUI | 创建 | 精灵创建一个新的精灵并执行以下步骤:
-
将其图集类型设置为科幻图集。
-
将其精灵类型设置为蜂窝精灵。
-
将其精灵类型设置为平铺。
-
将其颜色色调值设置为R:
255, G:120, B:120, 和 A:255。 -
将其深度值设置为
2。 -
将其尺寸参数设置为
3840x43。
-
好的。我们现在有一个带有精灵和碰撞器的EndOfScreen限制。现在,我们需要编辑EnemyController.cs脚本中的OnTriggerEnter()方法,以检查碰撞的对象是否具有DamageZone标签,并在需要时伤害玩家。执行以下步骤以完成此操作:
-
在项目视图中,选择我们的飞船预制件并打开附加到其上的
EnemyController.cs脚本。 -
在
EnemyController.cs脚本中,在OnTriggerEnter()方法的非常第一行,添加以下行以检查碰撞对象是否有DamageZone标签://Is the collided object a DamageZone? if(other.CompareTag("DamageZone")) { //In that case, hurt the player HealthController.Instance.Damage(30f); //Then, kill the enemy and don't go further StartCoroutine(Kill()); return; } -
保存所有脚本并点击播放按钮。现在,当敌人与 Viewport 的末端碰撞时,它们会被摧毁,玩家的生命值会减少!
现在,让我们添加另一种摧毁敌人的方法。
创建自毁代码
在屏幕上放置障碍物是不够的。我们将使用自毁代码来摧毁敌人。
每个敌人都有机会获得一个自毁代码。如果它有一个,将显示Code Encrypted的空滑动条将出现在相关敌人上方。
当玩家点击敌人时,黑客过程开始。当黑客完成后,会出现一个单词,如以下截图所示,玩家必须在键盘上输入它来摧毁它:

黑客滑动条
让我们从在Spaceship预制体内部创建黑客滑动指示器开始,按照以下步骤操作:
-
在项目视图中,选择我们的Spaceship预制体。
-
将它拖到层次结构视图中作为ViewportGameObject 的子对象。
-
通过导航到NGUI | 创建小部件并执行以下步骤来打开小部件工具窗口:
-
选择游戏 Atlas.
-
选择进度条模板。
-
选择按钮精灵用于空字段。
-
选择按钮精灵用于满字段。
-
-
选择我们的Spaceship实例,然后点击添加到按钮。
-
选择新的进度条GameObject 并执行以下步骤:
-
将其重命名为
DestructCode。 -
将其变换位置设置为
{-100,100,0}。 -
将UISlider的值设置为
0。
-
-
从DestructCode中选择背景GameObject。然后执行以下步骤:
-
将其颜色色调值更改为
{255,140,140,255}。 -
将其深度值设置为
2。
-
-
从DestructCode中选择前景GameObject 然后执行给定的步骤:
-
将颜色色调改为
{50,180,220,255}. -
将其深度值设置为
3。
-
好的,滑动条准备好了。让我们添加一个标签,它将显示Code Encrypted,并在黑客过程完成后变为自毁代码。
-
在层次结构视图中,选择我们的DestructCodeGameObject。
-
通过导航到NGUI | 创建小部件并执行给定的步骤来打开小部件工具窗口:
-
选择SciFi Font – Normal字体。
-
选择标签模板。
-
将颜色改为R:
255, G:215, B:190, 和 A:255.
-
-
选择DestructCodeGameObject,然后点击添加到按钮。
-
从DestructCode中选择新的标签GameObject 并执行以下步骤:
-
将其变换位置设置为
{100,0,0}。 -
将其文本设置为
Code Encrypted.
-
你的层次结构视图和Spaceship应该看起来如下:

太好了!让我们通过以下步骤将这些新修改应用到我们的飞船预制体上:
-
在层次视图中,选择我们的飞船GameObject。
-
点击检查器视图顶部的应用按钮以更新预制体。
-
从我们的层次视图中删除飞船实例。
好的,我们现在有一个滑动条将指示黑客状态,还有一个标签将显示自毁代码。
自毁代码
让我们在本地化文件中添加一些自毁代码和黑客状态。打开English.txt并添加以下行:
//Hacking status
CodeEncrypted = Code Encrypted
Hacking = [FF6666]Hacking...
//Self-Destruct Codes
Space = space
Neptune = neptune
Moon = moon
Mars = mars
Jupiter = jupiter
现在,打开French.txt文件并添加以下行:
//Hacking status
CodeEncrypted = Code Crypté
Hacking = [FF6666]Piratage...
//Self-Destruct Codes
Space = espace
Neptune = neptune
Moon = lune
Mars = mars
Jupiter = jupiter
好的!我们现在有了必要的本地化字符串。
将代码分配给敌人
我们现在将在EnemyController.cs脚本中添加一个新的SetDestructCode()方法,该方法将在初始化时为我们的敌人分配自毁代码。首先,让我们向其中添加必要的全局变量。
打开我们的EnemyController.cs脚本,并添加以下全局变量:
//Boolean to check if enemy is hacked or not
public bool hacked = false;
//We will need the Self-Destruct Code Label
private UILabel codeLabel;
//We will also need the hacking slider
private UISlider hackSlider;
//We will need to store the destructCode
public string destructCode = "";
//We will need a hackSpeed float
float hackSpeed = 0.2f;
我们必须设置这些变量。在Initialize()方法的末尾添加以下行:
//Get the hacking slider
hackSlider = transform.FindChild("DestructCode").GetComponent<UISlider>();
//Get the hacking status label
codeLabel = hackSlider.transform.FindChild("Label").GetComponent<UILabel>();
好的,现在,让我们添加一个SetDestructCode()方法,该方法将为敌人分配自毁代码。此方法将有一个包含要分配的自毁代码键的字符串参数,如下面的代码所示:
public void SetDestructCode(string randomWordKey)
{
//If the randomWordKey is not empty...
if(!string.IsNullOrEmpty(randomWordKey))
{
//... Get the corresponding localized code
destructCode = Localization.instance.Get(randomWordKey);
//Set the Label to "Code Encrypted"
codeLabel.text = Localization.instance.Get("CodeEncrypted");
}
//If the randomWordKey is empty, disable hacking slider
else
hackSlider.gameObject.SetActive(false);
}
好的,我们有一个设置正确破坏代码的方法。现在,让我们创建一个将被调用来启动黑客过程的Hack()协程。
黑客过程
Hack()协程将逐渐填充黑客滑动条,并在黑客完成时显示敌人的自毁代码。
使用以下代码片段将Hack()协程添加到EnemyController.cs脚本中:
IEnumerator Hack()
{
//Set the Label to "Hacking..."
codeLabel.text = Localization.instance.Get("Hacking");
//While hacking slider is not full
while(hackSlider.value < 1)
{
//Increase slider value, framerate independant
hackSlider.value += Time.deltaTime * hackSpeed;
//Wait for next frame
yield return null;
}
//Make sure slider is at 1
hackSlider.value = 1;
//Set the hacked bool to true for this enemy
hacked = true;
//Display the Self-Destruct code now
codeLabel.text = "[99FF99]" + destructCode;
}
现在,让我们添加一个OnClick()方法,当玩家点击敌人时,实际上将启动黑客过程。我们这样做的方式如下:
void OnClick()
{
//If the enemy has a destruct code, launch hacking
if(!string.IsNullOrEmpty(destructCode))
StartCoroutine(Hack());
}
好的,我们已经为我们的敌人设置了方法。现在,我们可以编辑我们的EnemySpawnController.cs脚本,在初始化新敌人时调用SetDestructCode()方法,并将随机破坏代码作为参数传递。首先,我们将添加必要的变量。
我们需要一个List数组来存储场景中的敌人。List类似于数组,但使用有用的方法(如Add()和Remove())更容易管理。为了使用List,你需要包含一个特定的库。
打开EnemySpawnController.cs脚本,并在文件开头简单地添加以下行,以及已经存在的两个其他包含行:
//Include Lists
using System.Collections.Generic;
现在,在EnemySpawnController类中添加这些新的全局变量:
//Chance for each enemy to have a destructCode
public float destructCodeChance = 60;
//Array of strings to store destructCodes keys
public string[] wordKeys;
//We will need a list of enemies
private List<EnemyController> enemies;
//We will need a static instance of this script
public static EnemySpawnController instance;
//This will store the current word typed by the player
public string currentWord;
好的,现在在新的Awake()方法中初始化一些这些变量:
void Awake()
{
//Store the instance of this script
instance = this;
//Initialize the List
enemies = new List<EnemyController>();
}
在我们继续之前,让我们在检查器视图中分配剩余的变量。保存脚本,回到 Unity,并从视口中选择我们的敌人GameObject。
现在,按照以下方式设置单词键数组和生成值:

很好,我们的单词键数组现在已经设置好了。让我们回到EnemySpawnController.cs脚本,并在其while()循环的末尾添加以下行:
//Create a new empty string for destruct code
string randomCode = "";
//If the random is valid, get a random word
if(Random.Range(0f,100f) < destructCodeChance)
randomCode = GetRandomWord();
//Set the enemy's the DestructCode newEnemy.SetDestructCode(randomCode);
//Add the enemy to the list of enemies
enemies.Add(newEnemy);
当敌人初始化时,前面的代码将其添加到敌人的List中并设置其自毁代码。现在,使用以下代码,让我们创建一个GetRandomWord()方法,它将返回我们预定义的单词之一:
private string GetRandomWord()
{
//Return a random Word Key
return wordKeys[Random.Range(0, wordKeys.Length)];
}
很好。我们的一些敌人已经分配了破坏代码。让我们添加一个方法来从List中移除敌人,该方法将在每次摧毁敌人时被调用。方法添加如下:
public void EnemyDestroyed(EnemyController destroyedEnemy)
{
//Remove the destroyed enemy from the List
enemies.Remove(destroyedEnemy);
}
打开EnemyController.cs脚本。在Kill()协程中,在Destroy(gameObject)行之前,添加以下行以从List中移除敌人:
//Remove enemy from the List
EnemySpawnController.instance.EnemyDestroyed(this);
完美。保存所有脚本并点击播放按钮。如果你点击具有黑客滑块的敌人,黑客过程开始,并在完成后显示破坏代码。
最后一步是处理玩家的输入,以检查他或她是否输入了敌人的自毁单词。
处理玩家输入
我们将使用EnemySpawnController.cs脚本的Update()方法来检查玩家使用键盘输入的字符。这些字符将逐个存储并与我们敌人的破坏代码进行比较。
打开我们的EnemySpawnController.cs脚本并创建一个新的Update()方法:
void Update()
{
//If the player has typed a character
if(!string.IsNullOrEmpty(Input.inputString))
{
//Add this new character to the currentWord
currentWord += Input.inputString;
//We need to know if the code matches at least 1 enemy
bool codeMatches = false;
//Check enemies' destruct codes one by one
for each(EnemyController enemy in enemies)
{
//If the enemy has a destruct code AND is hacked
if(enemy.destructCode != "" && enemy.hacked)
{
//currentWord contain the destruct code?
if(currentWord.Contains(enemy.destructCode))
{
//Yes - Destroy it and update our bool
StartCoroutine(enemy.Kill());
codeMatches = true;
}
}
}
//Did the word match at least 1 enemy?
if(codeMatches)
//In that case, reset the currentWord to empty
currentWord = "";
}
}
保存此脚本。现在,当你黑客一个敌人时,你可以通过输入其自毁代码来摧毁它!如果多个敌人具有相同的代码,它们将同时被摧毁。
注意
输入一个单词可能会滚动视口;这是因为 Q、A、S 和 D 被默认设置为水平轴和垂直轴。转到编辑 | 项目设置 | 输入,并分别从水平轴和垂直轴的 Alt Positive Button 和 Alt Negative Button 字段中删除(a, d)和(s, w)。
摘要
在本章中,我们使用了前面章节中关于创建简单游戏所学的所有内容。
我们创建了一个敌人生成系统,在场景中实例化敌人。刚体和碰撞体已添加以处理屏幕底部障碍物中敌人的碰撞。
我们还添加了一个与生命值相关的健康条小部件;如果四个敌人触摸屏幕的末端,游戏将重新开始。
使用了Update()方法来处理玩家输入,并将输入的单词与破坏代码进行比较,以便在需要时摧毁敌人。
目前,游戏相当简单。以下是一些增强游戏的想法:
-
添加更多自毁单词
-
在屏幕上显示玩家输入的内容(视觉反馈)
-
随着玩家摧毁敌人,逐渐增加敌人的出生率
-
随着玩家摧毁敌人,逐渐增加敌人的速度
-
添加评分系统
-
实现时间力量(提示:Time.timeScale)
-
实现炸弹力量
-
使破解时间取决于 barrierCount(更多障碍,破解速度更快)
-
包含使用右键点击移除 ActiveBarrier 的可能性
-
添加可点击的对象以恢复生命值;一些敌人会留下这些对象
-
如果玩家在 3 秒内摧毁多个敌人,则添加连击奖励
-
在困难难度中包含更难懂的单词
-
在屏幕外显示敌人的方向时插入一个视觉指示器(箭头)
-
在背景中添加更多视觉元素(如星系等)以帮助定位
-
显示带有主菜单和重启按钮的游戏结束屏幕
-
创建教程弹出窗口
如果你添加了之前的一些功能,我们的游戏将变得更加有趣。
为了提高你对 NGUI 的了解,你可以在www.tasharen.com/forum/index.php?topic=6754找到更多教程。
完整的 NGUI 脚本文档可在www.tasharen.com/ngui/docs/index.html找到。
就这些!我们现在已经使用这本书完成了与 Unity 和 NGUI 的工作。感谢您的关注,并祝您未来的项目一切顺利。






浙公网安备 33010602011771号