嗨，我叫马克。多年来，我一直想用软件制作我自己的视频游戏。
比如 Unity。Unity 是很多游戏背后强大的游戏引擎，包括：Cuphead、Neon White、Tunic
Outer Wilds，炉石传说，看火者，甚至口袋妖怪钻石重制版。
但我总是发现，太长的、各式各样的、弯的教程只会让我睡死过去。
我不可能三连收藏就假装会了，必须亲自动手啊，自己写bug才行。
所以去年我发明了一个实际可行的步骤，叫三步（三连？）技巧。
第一，只需要学习 Unity 的非常非常基础的知识。第二，用简单的方法巩固这些练习
然后，第三，在你前进的过程中解决剩下的bug。这绝逼有用！
在大约一年的时间里，我从借鉴 iPhone 游戏到开发自己的平台解谜游戏
游戏名：Magnets。我发布了一篇互动视频，已经有超过 10 万次播放
但是这些人看完总是问！第一步你怎么做？Unity这个软件太复杂了
你是如何学习基础知识的？
嗯，所以说，我就列了一份清单
不管我要做什么游戏，比如如何显示一个角色
在屏幕上移动它们。如何生成一个东西，然后再次移除它
然后，碰撞怎么搞，游戏结束呢，动画和声音效果等。
然后我通过搜索学不完的教程，还有 Unity 文档，
在谷歌上搜索深奥的关键词，并大量的测试和出现bug
这段视频的重点就是帮你省去这些麻烦。
这个视频是我在学习 Unity 时就希望拥有的教程。
所以在接下来的 40 分钟里，我们将使用引擎来制作 Flappy Bird.
不是因为我们想制作Flappy Bird，而是重做这款令人上瘾的 iPhone 游戏
我们会学习到刚才列出的所有内容，从生成对象到游戏结束。
本教程将涵盖从下载 Unity 到理解 UI 的每一步。
去写你的第一行编程代码，去构建一个你可以分享的游戏
你的朋友。然后，当教程结束时，我将分享一些具体的后续步骤，你可以
以便自己继续学习更多内容。听起来阔以吧？那就开始吧。
好的，让我们从网站上获取 Unity 开始。下载并安装 Unity Hub.
然后你需要创建一个免费帐户才能真正使用它。
完成后，你将被要求安装 Unity 编辑器，本教程中使用的是 2021.3 版本。
都2022年了，怎么还跟原始社会一样的，
网络慢的跟屎一样
还没完成，在“安装”下，点击 Unity 编辑器上的 ... 图标并选择模块。
你将看到 Microsoft Visual Studio 已被勾选，这是写代码的软件。
所以点击继续。并安装 Visual Studio.
在这个屏幕上，向下滚动并勾选 Unity 游戏开发，然后取消勾选 Unity Hub.
因为我们已经有了，我们不需要创建帐户来使用 VisualStudio，
所以跳过那个。不用加载了，我们稍后再打开。
好了，现在都搞完了。在 Unity Hub 中，选择新项目。选择所有模板。且使用 2D Core
这是一个空的项目，需要做些配置才比较适合 2D 游戏。
给你的项目命名，点击创建，让我们开始游戏制作。
第一步，我们将熟悉默认的 Unity 用户界面。
当熟悉不同的面板时，我们将顺便显示一只鸟在屏幕上。
好，这是 Unity 的默认屏幕布局，它分为四个面板。
首先，这里是项目面板。这将包含我们游戏中的所有内容。
-如精灵，声音效果，脚本，瓷砖，字体等。使用过程有些东西会被生成出来。
不过我们也可以从电脑上的其他地方拖进一些需要文件。
比如，我在 Photoshop 中制作了小鸟和烟斗的图片
就像这样将它们导入到我的项目中。我建议你别光看，最好做一下，顺便三连。
但如果你有毫无绘画天赋，去视频简介里拿这些资源吧。
下一个面板是层次结构。这包含了当前场景中的所有内容，
在大多数游戏中，叫关卡也可。我们将从制作鸟开始，
因此，右键单击并选择“创建 Empty”。创建了一个空的游戏对象..是什么鬼呢？
嗯，游戏对象本质上是一个看不见的容器。它在空间中有一个位置，一个旋转，
和缩放。然后，你可以用组件填充该容器-以添加额外的功能。
例如，如果我们添加一个精灵渲染器（SpriteRenderer）组件， 我们可以将鸟的图像放到游戏对象上。
当然，我们关卡中的东西都是一个带有组件的游戏对象，
不管是鸟啊，管道啊，甚至用户界面和摄像头，都是这种一个对象有多个组件。
最后在第三个面板中，即检查器（Inspector）——用于查看和配置游戏对象的属性。
所以，当我们选中了一个新的对象，空的游戏对象，我们就可以在顶部修改名称-比如：Bird
我们可以看到 Transform 这里可以改变游戏对象的位置，旋转和缩放。
现在，我们可以按“添加组件”、“拾取渲染”和“拾取精灵渲染器”。为了实现这一点，
我们需要填充 sprite 字段-因此只需将鸟图像从
项目面板把它拖进来这里，这样就有图片显示出来了！
当然，这将在第四个也是最后一个面板中显示，
场景视图。在这里，我们可以看到当前场景中的内容，
如果你愿意，你可以使用这些工具来移动东西，缩放它，等等。
这个部分有一个额外的游戏视图标签，它向我们展示了游戏的外观。
运行时的主摄像头。此外，从这个下拉菜单中，我们可以设置分辨率或屏幕比例
为了更好地了解它在播放时的样子，所以我将选择 1920 乘以 1080。
哦，这只鸟占据了太多的空间。我们可以缩小规模，但实际上
把镜头拉远。就像我之前说的，摄影机本身就是层次结构中的一个游戏对象。
它有一个摄像头组件，里面有我们可以调整的属性。通过改变大小，我们可以缩小。
我也要改变背景颜色，搞个可爱的。
我们现在可以按下上面的播放按钮开始..
世界上最无聊的游戏诞生了。好吧，让我们把它变得更刺激一点。
快速回顾一下。默认情况下，Unity 有四个面板。项目包含我们的所有游戏内容
层次列出了当前级别中的所有游戏对象。检视器（Inspector）让我们看看。
并更改这些游戏对象。我们可以在场景视图中看到级别。
游戏对象是一个不可见的容器，我们可以用组件填充它，就像精灵渲染器一样。
在第二步中，我们将使用更多的组件将鸟变成
受重力影响的物理对象。然后我们要写一些编程代码，
让小鸟在我们按下空格键的时候飞起来。
所以让我们给我们的鸟添加另一个组件：一个2D刚体（Rigidbody2D) .
这把我们的鸟变成了物理物体，有重力。所以当我们点击播放时，鸟就会掉下来，从屏幕上掉下来，凉凉了。
我们还希望这只鸟能够与其他物体互动，所以让我们添加一个对撞机（Collider2D）。
2D圆形碰撞器 CircleCollider.
回到场景视图中，我们可以看到碰撞器有一个绿色的轮廓。对我来说有点偏离中心，
所以我将使用偏移（offset）来移动它。一个小游戏设计技巧，
如果我们使对撞机比图像小一点，它会让玩家容易通过管道，即使他们看起来摸到了边缘
它给了游戏一点宽容，让玩家觉得赚到了。
现在要添加的最后一项内容是：脚本（Script）。这本质上让我们自定义自己的组件。
但我们必须使用编程代码自己编写。组件列表选择: New Script
就叫它 Bird 脚本。加载后，双击脚本打开它
这将在我们之前安装的 Visual Studio 中打开该文件。
所以，欢迎来到编程！别慌，我保证不难，我们会慢慢来
我们用 C Sharp 写的，那是编程语言。
现在唯一需要担心的是这两个部分：启动和更新。
Start 用于在启用此脚本后将立即运行一次。
启用脚本时，Update 将持续运行，会不停的允许。
每一行代码，每一帧。一遍又一遍。
所以我们现在要用代码做的主要事情是-好吧，如果我们回去到 Unity
查看组件中的这些数字和文本字段，只要我们能在Unity中编辑它们
基本上，我们就可以编写代码来在游戏运行时更改这些属性。
举个愚蠢的例子，我们将在一秒钟内删除它。在开始阶段，
我们可以输入 gameObject 它指的是这里。然后输入一个 "."。你将看到出现一个列表，
许多项目引用了 Inspector 中的内容，如 IsStatic，标记、图层和名称
所以让我们选个 name。然后写一个等号，然后在引号中，给我们的鸟起个名字。
最后，我们必须始终使用分号（;）来标记一行代码的结束。
在我们回到Unity之前，我们必须保存一下脚本。
现在，当我们运行游戏..游戏对象的名称已更改，怎么样牛不牛。
好的，删除那个代码。那只是给傻子看的。
不过我们学会了使用代码与游戏交互了。我们可以通过写代码来操作某个东西。
比如游戏对象，然后名称，然后命令
将其更改为 Bob Birdington. 会经常写这种东西。
其实，我们真正想做的是..在刚体 2D 的组件中，在“信息”（Info）下，
我们会看到一个灰色的速度场。
想写一些代码来给鸟增加向上的速度，使它飞到空中。
问题是…。最初，脚本只能与游戏对象的顶部进行操作。
现在，这个脚本完全不知道其他组件。
所以我们需要先解决这个问题。我们需要在这个脚本上做一个特殊的插槽
一个 RigidBody2D-这样我们就可以与操作它并向它发送命令。这称为引用。
我们将在这里创建引用，在类名和 Start方法。
我们将编写 public RigidBody2D myRigidbody.
所以我们现在有一个插槽来存储 RigidBody2D.我们有一个可以参考的名字-
以确定我们需要的是这个特定的刚体 2D. 因为我们公开了它，我们从脚本外部访问此插槽。
所以如果我们回到 Unity，我们会看到脚本组件现在有一个用于 RigidBody2D 的字段。
我们可以把组件拖到那个槽里，放下。
我们已经在脚本和刚体之间建立了一条通信线路。
好的，回到 Visual Studio. 在 Update 中，我们可以输入 myRigidbody. 然后是点。
现在看看我们可以调用的所有东西。比如：角阻力，重力比例，质量
这些都是组件上的属性，我们需要的是速度（velocity）。
我们想把它设置为一个新的数字值，所以，就像前面的名字一样，我们将写一个等号。
现在我们在这里写的实际上是一个向量，它是两个数字，表示一个二维空间。
在这种情况下，它用来表示鸟飞行的方向。
我们想要鸟直接上升，所以是 (0, 1) 是可以的。我将使用 Vector2.up，
这是(0, 1)的内置简写。为了给它更多的能量，
我要将该矢量乘以一个数字。说，10，这应该可以把鸟送上西天。
现在，就像我之前说的，Update 中的任何代码都会每一帧反复运行，
所以如果我们保存脚本，并在 Unity 中点击播放..我们的鸟飞了。祈祷！
这样不行，最好只在玩家按空格键的时候。那么
是时候使用最基本的编程代码了：if 语句。
if 语句就像一个门。
你可以用大括号包围一些代码，代码的每一帧都将完全不执行
除非，游戏满足一些特定的条件
在门上-在这种情况下，门是打开的，代码被读取并执行。
所以我们想说“如果玩家按下空格键，然后加上向上的速度”。
为了做这个..我们可以写 if，然后在括号里写条件。
这次我们不是在讨论一个组件，而是在讨论 Unity 本身——特别是它的输入系统。
那么我们将编写输入。然后我们可以选择 GetKeyDown，并在括号中选择 KeyCode.space.
如果在该帧上按下了空格键。然后我们输入 “== true”。
关于等号的简要说明——我们用一个等号来使左边的东西等于右边一个值。
我们用两个如果我们只是在检查左边的东西和右边的一样
懂吗？
无论如何。所以这个代码说..如果空格键刚被按下，那么..然后我们会
将代码放在大括号里。
所以，现在在 Update 中-游戏的每一帧都会走到门口，然后被问到嘿，
空格键是否已按下？如果是，代码将运行，鸟将扇翅膀。如果没有，
它将跳过花括号中的代码，并在下一帧中重试。
所以-保存脚本，并返回到 Unity. 我们现在可以点击播放和 Tada：
当我们按空格键的时候，鸟就会飞起来。
我们现在已经创建了一个角色，并使用键盘控制它。这就是一个电子游戏。牛！
然而，它就像垃圾。这翅膀扇得不对，感觉不像原作。
所以我们可以改变这个数字。保存。回Unity，运行游戏。不太对。
停。更改一下。保存。不过好傻啊，我们搞点更聪明的办法吧
首先，我们要创建一个变量。让我们回到脚本的顶部和底部
我们参考刚体，让我们制作一个名为 flapStrength 的公共插槽。
一个浮点数（float）-是一个可以有小数位的数字。
然后回到我们的更新代码中，我们将 Vector2.up 乘以 flapStrength，而不是 10。
现在，回到 Unity 中，你将看到脚本组件有一个新字段：flapStrength
我们可以随时改变这一点，让游戏手感更有感觉。
我们甚至可以在游戏中更改它，但请注意，你更改的任何内容
当你按下“停止”时，正在运行的游戏不会保存。这意味着你可以玩
你可以尽情享受你的更改，而不用担心搞砸你的游戏。
所以，如果我们弄乱了flapStrength，还有刚体重力缩放
我们希望能得到一些感觉良好的东西。
来回变换数字：观众老爷，这就是游戏设计！
接下来复习一下。
在游戏运行时，我们可以使用代码来更改组件的属性。
默认情况下，脚本不能与游戏对象上的其他组件对话。
你必须通过存储对该特定组件的引用来建立通信线路。
我们在代码中创建引用，然后通过拖放在 Unity 中填充它。
当脚本出现时，Start 中的代码只运行一次。Update 中代码每一帧连续运行，
但是，我们可以使用 if 语句跳过一些代码，除非满足条件。
我们可以使用公共变量来改变某些Unity 的 Inspector 中的值
即使在游戏运行时。
好的，Flappy Bird 的秘密是，虽然它看起来像一只鸟
在一个充满管道的世界里飘动，但实际上是
鸟保持完全静止，管道在屏幕上移动。所以在第三步中我们
让管道在世界上生成，在屏幕上移动，然后出去屏幕后移除它们。
我们将从制作我们想要生成的对象开始。
这将是从左到右穿过屏幕的两根管道。
让我们创建另一个名为“管道”的游戏对象。现在把它准确地移动到鸟中间，
以获得正确的相对位置。然后我们将在这个中创建另一个对象，称为顶管。
这是第一个和父对象和子对象层级有关的游戏对象。
这边我们可以嵌套多个游戏对象，并通过移动父对象一次移动所有子对象
所以让我们还是重复像制作鸟的过程一样，为管道图像添加精灵渲染器。
并添加一个碰撞器-这次是一个 2D 的盒子碰撞器（BoxCollider）。我们不需要刚体，
因为它不会受到物理学的影响。然后我们可以把它移到鸟的上方，但保持 X 的位置为零。
最后，我们可以复制整个顶部管道对象。然后叫它底管。然后翻转一下
通过将 Y 刻度更改为-1，将其颠倒。然后把它移到鸟的下面。
如你所见，如果我们改变管道父游戏对象，两个管道都会以父对象作为轴点
移动，缩放，并与其一起旋转。
将脚本添加到此父对象，使其在屏幕上移动。
我们首先为 moveSpeed 创建一个变量。如果我们在这里给它一个数字，
它将填充此值作为 Unity 中的默认值。但我们以后可以随时改变它。
然后，我们将在更新中编写代码来移动对象。如果我们能
键入 transform.position.x，然后直接更改此数字，但其实不是，Boo，
你必须一次完成整个Vector的更改。哦，这次我们要用Vector3，而不是矢量 Vector2，
因为 position 有三个数字。尽管我们在制作 2D 游戏，但从根本上说
Unity 仍然是一个 3D 引擎，因此它通过 Z 值来跟踪对象的深度。
所以，我们要做的是。我们将transform.position, 然后等于。
我们想要添加到它的位置，所以再次编写
transform.position = transform.position + (Vector3.left * moveSpeed);
返回 Unity，按“播放”。卧槽，太快了。
现在，你可能认为你可以把这个 moveSpeed 变量改成
像 0.001 这样的小数字。这会起作用——但这实际上不是问题所在。
你可以看到，Update 中的代码只是尽可能频繁地运行。事实上，如果我们在游戏视图中查看属性，
我们将看到游戏以每秒超过 1000 帧的速度运行。嘿，
对不起，PlayStation 5，每秒 120 帧？呸，这和《Flappy Bird》没什么关系。
真正的问题是游戏可能在不同的计算机上以不同的速度运行，
我们不希望管道移动得更快或更慢，这取决于你的电脑。
真实游戏其实也犯过这个错误——在《黑暗之魂 2》中，武器耐久度随每帧耐久度下降
所以你的剑在每秒 60 帧时会比每秒 30 帧快两倍。那真是太棒了。
幸运的是，这是一个很容易解决的问题。我们只需乘以 Time.deltaTime.
这确保了无论帧速率如何，乘法都会计算出相同的值。
不过在velocity的代码上则不需要它，因为物理运行在它自己的小时钟上，
如果你想知道更多-关于这件事，或者任何事，
Unity 文档是一个好地方。你会找到更多和示例代码。
好的，现在有了这个解决方案，我们的管道可以在屏幕上平滑地移动。可爱的。
接下来，我们要创建一个将不断生成新管道的系统。首先，
从层次结构中获取父游戏对象并将其拖动到项目中。这将创建一个预制游戏对象（Prefab）。
或叫预制。这就像一个游戏对象的蓝图，我们通过这个蓝图
创建新的整个游戏对象的另外版本，会包括所有子对象、组件和属性。
在我们继续之前，我们现在可以删除层次结构中的原始内容。拜拜。
让我们创建一个名为“管道产生成器”的新游戏对象。
我们会把它放在摄像机的右边。我们会为它做一个脚本。
脚本将每隔几秒生成管道预置的新版本。因为管道已经
有向左移动的代码，管道将在生成后立即自动在屏幕上移动。
我们将编写一些代码来生成我们刚刚制作的预置。那么
我们将从引用预制件开始。
在这里，我们将输入公共游戏对象管道。
然后在 Unity 中，我们将使用相同的拖放方法来填充插槽，但这一次，
我们将从“项目”面板中拖动预置，而不是组件。
现在，Unity 有一个很好的内置生成对象的方法。
新游戏对象。我们将输入 Instantiate，然后打开括号。
在这里，需要填一些额外的细节。我们实际上可以通过翻阅这些找到不同的，怎么说呢，食谱吧？
我猜？这个看起来不错，它将在指定的位置和旋转创建一个对象。
因此，对于游戏对象，我们可以输入 pipe. 对于位置
我们可以只输入 transform.position 来获取对象的位置。
意思是说，会把管道生成在生成器所在的位置。
并且对于旋转，让我们只使用 transform.rotation 和生成器也相同。
让我们运行它，天啊，这不是我们想要的。生成效果很好，
但他们每一帧都在出现，我们希望他们出现。
在一个我们可以控制的好时间里。所以，回到 Visual Studio.
我们现在要做的是写一些代码来制作一个计时器。这将计入
指定的秒数，运行一些代码，然后再次开始计数。要做到这一点，
我们需要做几个变量。生成器应该指它应该有多少秒生产一个管道
然后计时器是计数的数字。我们可以做这个。
一个私人的，因为我们不会在编辑器或其他任何地方更改它。
在Update中，我们将执行另一个 if 语句。此时，如果计时器小于 spawnRate，
然后我们让计时器加一。因此，我们将采用当前的计时器，
并将 time.deltatime 添加到其中。这将创建一个计数每一帧的数字，
无论你的计算机的帧速率是多少，它的工作方式都是相同的。
实际上，我们可以通过将其更改为 += 来缩短它，但是，不要觉得你需要将代码设置为尽可能短
只是为了避免大佬们不屑的评论。
如果："timer = timer + xxx" 更容易阅读和掌握，那么这绝对没问题。
你熟练了，可以随便使用。
现在，在我说 if 语句之前，它就像一扇门。
我们可以在它的旁边添加另一个门，否则。这意味着，
如果不满足条件，则跳过该代码，并在 else 中执行该代码。
因此，我们将在这里放入生成代码，并将计时器重置为零。所以现在，每一帧，
询问计时器是否小于生成率。如果是的话，那就把计时器算起来。如果不是
即计时器实际上已经达到或超过了生成速率，然后生成一个管道并再次启动计时器。
把这个放在 Unity 里-很好。我对此很满意。唯一的问题是。
我们必须等待第一个管道生成的时间很长。如果这个马上出来就好了，对吧？
现在，我们可以将代码复制并粘贴到 Start 中，这样它就会在 Start 中发生一次。
然后在更新中一遍又一遍地发生。但这是个坏主意。你通常应该试着
避免在多个位置使用相同甚至相似的代码。
如果我们想要改变生产的工作方式？我们必须各处寻找并修改它，不是很好。
相反，我们可以将代码放在一个新函数中，然后运行它
所以在这里，下面的Update，但在最后的花括号上面-我们将
创建一个名为 void SpawnPipe（）的函数。然后将实例化代码剪切并粘贴到其中。
现在，我们只需在 Update 和 Start 中编写带有空括号的 SpawnPipe.这将运行
执行这些行时该函数中的所有代码。做完这件事，
它会让游戏一开始就有一个管道，并且每次计时器达到最大值时都会产生新的管道。牛了。
然而，这是一个相当无聊的游戏，对不对？管子总是从中间出来。我们想要他们
以随机的高度出现。请记住，当我们编写实例化代码时，
我们必须选择一个物体出现的位置。我们将更改该值。
现在，管道总是在与生产器相同的位置生产。我们想要 X 值
是一样的..但对于 Y，我们想要在生产器上方或下方的某个位置随机选择一个点。
因此，让我们为高度偏移创建一个公共变量，可能是 10。
然后我们将创建一个名为 lowestPoint 的浮点数。因为我们在函数，
而不是在脚本的顶部，这意味着它只能在函数中使用。但是，
这也意味着我们可以通过计算来设置它。
因此，我们将执行 = transform.Position.y - heightOffset. 然后我们再做一个。
一个是最高点，但这次是加上高度偏移。这样我们就得到了这两个数字。
然后，我们将替换实例化代码中的 transform.position.
我们要写一个新的Vector3，每当我们指定我们自己的数字时，我们必须用 Vector3
然后在括号中，我们将 x、y 和 Z 值指定为三个不同的浮点数。对于 X，
我们希望这与生成器相同，所以我们将执行 transform.position.x.
但是对于 y，我们可以做随机范围。在括号里，我们可以提供一个最小值和最大值。
从中挑选。这是最低点和最高点。然后用 0 表示 Z，并关闭括号。
回到Unity..好的！管道将在这两个数字之间的任何位置生成。
哦最后一件事。每次这些管道生产时，它们都会出现并向左移动..永远。
这不是很好的方式——他们在屏幕外什么也不做，
然而，它们仍然在内存中，每一帧都在运行代码。如果太多，它们就会
把你的电脑内存全吃掉，然后崩溃。所以让我们解决这个问题。
现在，我们可以制作一个计时器，并在几秒钟后删除管道。但是相反，
我们将检查管道的 X 位置，如果它超过了某个点，则将其删除。我们会借的
这只鸟找出了屏幕左边的 X 坐标。看起来大约是零下 45 度
在管道移动脚本中，我们将为死区添加一个浮动。-45. 然后是一个简单的 if 语句-if
如果 Transform.Position.X 小于 DeadZone，则销毁保存此脚本的游戏对象。
在 Unity 中运行它，然后，砰，他们死了。
让我们再做一件事，像个学生一样。就在销毁之前，
让我们编写 Debug.Log，并在括号中删除管道。然后，回到 Unity 中，
你将看到另一个面板我在 UI 演示过程中跳过了-它是 Project 旁边的一个选项卡，称为 Console.
然后当我们运行游戏..每次删除管道时，我们的消息都会发送到控制台。
这是一种非常有用的调试代码的方法，因为我们可以准确地找出代码的目的。
回顾时间！
游戏对象可以转换为预制件，
通过将它们从层次结构中拖放到项目中。
然后你可以把它们拖到场景中, 我在我的益智游戏中使用预制件来创建关卡。
比如说。或者你可以在游戏中创建一个生产器来实例化新的生产器。
计时器是一种使代码在特定时间间隔内发生的好方法，
但始终使用 time.deltatime 来保持不同计算机之间的一致性。
if 语句可以有一个 else 的门，以使代码在条件未满足。
你也可以有其他的 if，来制作更复杂的门。
如果不再需要游戏对象，你应该尝试删除它们，以释放内存。
好的，我们下一步是记录玩家的得分，
并在用户界面上将其显示给玩家。然后，
我们希望每次鸟穿过管道时，分数都能上升一分。
因此，请记住，游戏对象不一定是游戏中的物理对象
世界就像一个角色或一个敌人-它可以是一个完全看不见的管理者
这只是跟踪关键数据，如HP、时间或分数。然后，
我们可以使用用户界面让玩家看到这些信息。
所以让我们开始制作 UI。像其他东西一样，在层级面板，它是一个游戏对象。
这一次，向下到 UI 并选择文本，它可能在Legacy
我们需要将场景视图放大到非常远的地方，才能真正看到 UI.
为了确保 UI 在每个设备上看起来都一样，我们将选择这个新的画布游戏对象
将画布缩放器组件的 UI 比例设置为随屏幕大小缩放，并选择合理的
参考分辨率-我将再次使用 1080p.然后，我们可以移动文本。你会注意到
该 UI 具有 Rect Transform，而不是正常 Transform。最重要的是要注意
你真的先不要使用 scale改变大小，而是用宽度和高度。
然后，我将增加字体大小并设置默认值。
文本设置 0. 然后检查它在游戏视图上看起来都很好。
好的，现在我们要做一个脚本来存储玩家的分数，
并将 UI 上的数字更改为该分数。
我们将制作一个名为逻辑管理器（Logic Manager）的游戏对象。
我们会给它一个脚本。此脚本将跟踪高级信息
比如玩家得分。它将具有我们可以运行的各种基本功能。
因此，我们将删除 “Start”和“Update”，我们在此脚本中不需要它们。我们可以随时添加
如果我们后面还要，可以加回来。我们想为玩家的分数存储一个数字。这一次，
我们不想要浮点数，因为我们只想要整数。所以让我们做一个整数，
这是个整数。没有小数位。
因为我们想要更新我们刚刚创建的 UI 文本，所以我们将一如既往地
必须做一个引用。但是，好像没找到合适的。
啊，好吧。默认情况下，脚本只加载Unity所需的基础内容。
但如果我们在顶部输入 UnityEngine.UI;，我们现在可以
访问更多的功能-在这种情况下，是用户界面的东西。现在我们可以参考文本。
我们需要将文本组件拖回 Unity 中的这个字段。因为我们正在引用一个组件
在另一个游戏对象（UI 上的文本）上，最好的方法就是
拖动整个游戏对象进入我们的插槽。这将自动为我们找到文本组件。方便。
所以现在我们要做一个函数。我们称之为 AddScore.
还有因为我们将从其他脚本运行此函数，并将其设置为 public void.
这个函数需要做两件事。在玩家的分数上加一。很容易，
我们现在知道该怎么做了。并将 UI 上的文本更改为此数字。哦，文本框是
寻找一个字符串-字符序列-我们的分数是一个整数。他们看起来一模一样
对我们人类来说，但程序是挑剔的。请注意，通过将.ToString（）添加到游戏分数中，很容易修复。
为了确保这一点，让我们自己来运行这个函数。
从Unity本身。我们需要做的就是在函数上面写上 ContextMenu 和一个名称。
现在，在 Unity 中，当游戏运行时，点击这个脚本上的小点并选择函数。
好看的！这类东西在测试中非常方便。
好的，现在我们知道函数运行了，我们特别想运行它。
当鸟在管道之间穿行时。而做到这一点的方法是碰撞。
现在，如果两个物体有碰撞器，它们会互相撞击-事实上，在我们的游戏中，
这只鸟已经撞进了管道，因为我们在两个管道上都增加了对撞机。然而-你
也可以有不可见的碰撞器，称为触发器（Trigger）。它们不会造成真正的碰撞，
但是它们确实让你知道两个对象已经接触-并且你可以在此时运行代码。
所以我们要在管道之间放一个触发器，
所以我们知道这只鸟穿过了它们。然后在那个时候，我们将运行 addScore.
让我们打开管道的预置。我们将制作另一个名为“middle”的游戏对象
它需要一个盒子对撞机。让我们把它做成这种形状。这次我们要
在 Is Trigger 框中打勾。最后，让我们为这个新的中间游戏对象添加一个脚本。
在 Update 下面，输入 OnTrig，按Tab，自动将帮助我们输入 OnTriggerEnter2D.
此函数意思是，任何内容进入碰撞器的第一次时都将运行
还有 OnTriggerExit 和 OnTriggerStay，供将来参考。
我们要运行之前编写的 addScore 函数..除了。啊哈。再一次，
这个脚本不知道游戏中的任何其他脚本，直到我们引用它。
所以我们可以编写公共逻辑脚本逻辑。但回到 Unity，你很快就会意识到
你不能把脚本拖到这个槽中。你不能把它从项目中拖出来
我们只能与游戏对象上的脚本实例来操作。
但我们也无法从场景拖动到预置中。这是因为场景中还不存在管道，
只有当游戏运行时，生产器开始制作管道时，它才会存在。
因此，我们需要使用代码来填充此引用。
这需要在管道首次生成时进行。
为此，我们需要帮助代码找到逻辑脚本。
要做到这一点，以游戏逻辑对象为例，查看检查器的顶部：
你会看到标签。从下拉列表中选择“添加标记”。创建一个新的标签，
并确保你回到游戏对象并实际设置这个新标签。你不会忘记的
在你的Unity职业生涯中，这样做大约 8000 次，所以期待吧。
现在，回到 PipeMiddleScript 中，
在 Start 下，我们可以写 logic = Gameobject.FindGameObjectWithTag("logic")。
这将在层次结构中寻找第一个带有logic标签的游戏对象，
在我们的例子中，场景中永远只有一个，
所以我们知道它总会找到合适的——但一定要注意这一点。
然后我们可以添加.GetComponent（）；
所以，一旦一个新的管道产生，它就会通过层次结构来寻找一个有logic标签的游戏对象。
然后，它将查找该对象的组件以找到脚本LogicScript。
如果它找到一个，它会把它放在我们的参考槽中。
它完成了与拖放组件到插槽完全相同的操作。
当然，区别是通过代码在运行时做到的。
所以现在，管道的中间脚本可以找到逻辑脚本并与之操作。
如果我们编写 logic.addScore，它将运行该代码。回到Unity，
点击播放，如果我们一切都做得很好，当我们通过管道之间时，分数将上升一分。
哦，不过为了后面更好的做其他事情，让我们确保通过管道的确实是这只鸟。
我们将通过把鸟放在一个层（Layer）上，并设置碰撞是否有效。
转到鸟的游戏对象，这一次，而不是标签，我们将改变鸟的层
添加一个新的，记得回来设置一下，并记下数字。
现在，在管道的中间脚本上，我们可以在 addScore 周围添加一个 if 语句，
并检查刚刚发生的碰撞是否与属于 Bird 层上的游戏对象。
当我们谈到这个话题的时候，还有一点需要做的。回到Logic Script。
让我们以 addScore 函数为例，在这些空括号中，
我们将写下 int scoreToAdd. 然后，我们将添加 soreToAdd，而不是添加一个 1。
然后，在管道中间脚本中，我们可以在 addScore 后面的括号中写入 1.
对的，现在，这和我们以前做的事情完全一样。但是，你肯定能猜到，
稍后，你可以在游戏中添加一些其他目标，例如，在你的得分上添加 5。
这使我们可以使函数更通用，因为它可以使用
以不同的方式，从不同的地方。我认为成为一名优秀程序员的一部分，
是让东西变得不那么死板，并为未来的想法保持开放。
这使得对设计进行迭代变得更加容易和快速。
对的！回顾！
UI 只是另一个游戏对象，但如果我们想在这些组件中引用
我们需要将 UnityEngine.UI 添加到脚本的顶部。
游戏对象可以是完全看不见的东西，
只是在那里管理规则、逻辑、分数等等。
如果我们想在一个游戏对象不在场景中的时候引用一个组件，
我们需要在运行时找到该组件。
一种方法是使用Tag、FindGameObject 和 GetComponent.
只要有引用，就可以从另一个脚本运行公共函数。
对那个脚本。我们甚至可以在该函数运行时传入变量。
当两个物体接触时，碰撞和触发可以用来使事情发生。
说到碰撞，让我们进入下一步……
最后一步是添加失败状态。当鸟撞到管道时，游戏就结束了。
我们将通过在屏幕上制作一个游戏结束界面来做到这一点，并让它在鸟碰到管道坠毁时出现。
游戏结束界面将有一个按钮，我们可以用它来重置游戏。
首先，让我们在屏幕上制作游戏结束界面。在画布游戏对象上，
添加一个新的空的游戏结束界面。然后，在该父项中，添加“游戏结束”文本。
还有一个按钮-也在 Legacy 下。调整它的大小。
和改变按钮上的文本，该文本可以作为按钮本身的子项找到。
所以回到按钮游戏对象上，在按钮组件上，你会看到
点击时显示的这一点。这是一个事件，它允许我们调用
游戏对象上的公共函数。所以让我们做一个重启关卡的函数。
我们可以将这段代码放在逻辑脚本中，
在我们的 addScore 函数下面。如果你愿意，你可以做一个单独的脚本，
但我觉得这很好。让我们创建另一个名为 RestartGame 的公共函数，
在这里，我们将编写代码来重新启动场景。就像以前的用户界面一样管理场景
然后，我们需要在顶部添加一行-这一次，使用 UnityEngine.SceneManagment.
现在，在我们的函数中，我们将调用 SceneManager.LoadScene.
这需要一个场景的名称。其实就是场景的文件名。但是因为我们想要当前的场景，我们
可以简单地输入 SceneManager.getActiveScene，方括号，.name。
现在回到 Unity 中，向此按钮添加一个事件。
然后拖入逻辑游戏对象。并找到 RestartGame 函数。
简单测试一下..。好的，每次我们按下按钮，游戏就重新开始。
现在很明显，我们不想让它一直出现在屏幕上——只是在我们失败的时候。那么，
我们可以将整个游戏置于屏幕游戏对象之上，并使用此复选标记将其禁用。
那我们就让它在鸟撞进管道的时候出现。
我们先写函数。同样，在逻辑脚本中，让我们为 GameOver 创建一个公共函数。
我们需要对屏幕上的游戏对象进行引用。
并用Unity来填充它。
然后我们可以简单地在这个函数中输入 gameoverscreen.setActive true.
所以我们想让这个函数在鸟撞进管道时触发。
回到 Bird 脚本，让我们重用之前的代码来访问逻辑脚本。
是的，我们可以在 Unity 中拖放引用，但是，
我们现在已经编写了这段代码。然后我们将对触发器代码执行类似的操作，但这次
我们将使用 OnCollisionEnter2D，因为管道是实体对象，而不是设置为触发器。
当冲突发生时，使用 Logic.GameOver 触发游戏结束脚本。
回到Unity..这是可行的，但我们仍然可以在屏幕上玩游戏。不是很理想。
我已经讨论了几个关键的变量类型。浮子
整数是数字。字符串通常用于文本。另一个重要的是布尔，
布尔值的缩写。这是一个非常简单的类型，要么为真，要么为假。在上方，
或者关掉。是，或者不是。这是一种简单地检查或更改某物状态的好方法。
所以让我们有一个名为 birdIsLive 的布尔，并确保它开始为真。
然后，当碰撞发生时，我们将 birdIsLive 设置为 false.
最后，我们将在第一个 if 语句中添加一个额外的条件。
比如说，如果空格键刚刚被按下……由两个“”符号组成。而 birdIsLive 是等于真。
实际上，我们不需要加上这个 == true 的东西。是可以的。
没有它也一样。但是，再一次，这取决于你-也许写出完整的代码更容易阅读。
无论如何，现在，如果鸟死了，它就不会拍打翅膀，这对我来说似乎很合乎逻辑。
最后要做的是构建游戏。这真的很容易。选择文件、生成设置和生成。选择一个文件夹
你的硬盘。让Unity发挥作用。然后你可以打开这个文件来玩你的游戏！令人惊讶的。
在很短的时间内，我们制作了一个非常实用的游戏。
更重要的是，我们学到了很多关于Unity的基本知识。
我们创造了一个角色，它会根据我们的输入而移动。
我们有在计时器上生成新对象。我们已经创建了一个显示分数的 UI，
并在条件满足时使分数打勾。我们有能力结束一场比赛，
然后重新开始。
现在，我应该注意到，有不同的，也许更好的方法来做几乎所有的事情。
在本教程中。例如，我使用了 Unity 的传统方法来检查输入
Unity他们后来开发了一个非常非常好的输入系统。但要复杂得多
使用-所以这个简单的方法现在很好，你可以稍后再看看新的输入系统。
当你感觉更自信的时候。对我来说就是这样。还有 TextMeshPro，
已经取代了旧的Legacy用户界面系统-所以你会想要升级，在某些时候。
无论如何，这些都是对制作各种游戏有用的课程。
但是..游戏还没有完全结束。还有一些事情要解决。尽管，
我不想告诉你怎么做每件事。所以我会给你一些建议完成游戏，
我想让你自己试着搞定这些。
首先，如果小鸟从屏幕上消失，我们也需要让游戏结束。
这应该不会太难。还有一个错误，分数会一直上升，
即使在比赛结束后。也试着解决这个问题。
我们也想要声音效果。我想让你在逻辑管理器中添加一个音频源组件。
用音效文件填充它。在脚本上引用它。当分数上升时让它播放。
然后，我想让你玩一下粒子系统，让云出现在游戏中。
接下来，打开动画窗口，为小鸟添加一些拍动的翅膀。
然后我想让你添加另一个场景来制作一个标题菜单界面，
所以游戏不会立即开始行动。这里有一个提示：
你需要将此新场景添加到“构建设置”（Build Settings）窗口。
最后，如果你想要一个真正的挑战-使用 PlayerPrefs
将玩家的高分保存到硬盘上，并在用户界面上绘制。
对于其中的每一个，你可能会想要谷歌相关的术语，阅读 Unity 文档，
观看一些快速教程视频，或在下面的评论中寻求帮助。
接下来，你可以扩展一下《Flappy Bird》。发挥创意
并添加原始 iPhone 游戏中没有的想法或设计。
举个例子，我给了它发射导弹的能力，
然后我在管道上添加了目标。你现在必须用导弹击中目标才能打开。
一个你可以穿过的缝隙。它非常酷，为简单的游戏增加了更多的深度。
事实上，我很想看看你如何扩展原来的游戏。如果
你做一些有趣的东西，录一些片段，放到 YouTube 上，
并在评论中放置一个链接。将来我可能会介绍其中的一些。
最后，我建议你
做另一个简单的游戏，并尝试在 Unity 中重新制作它，就像我们现在所做的那样。
这是一个伟大的技术，因为你不必担心艺术或设计。
只是代码。你将面临的解决问题的难题就是一个很好的例子。
真正的游戏开发会是什么样子。
这方面的好候选人包括乒乓，太空入侵者，突破，弹出锁，愤怒的小鸟，
各种各样的 WarioWare 迷你游戏，如果你的互联网坏了，还可以在 Chrome 上玩恐龙游戏。
所以，在这个视频中，我想教你
Unity背后的基本概念-但是，剩下的就看你的了。
幸运的是，我认为这种亲自动手，自我指导，从错误中学习的风格
学习是最有趣、最有效的方法，可以让你坚持下去。但我们会看到的！
在下面的评论中让我知道你的进展。如果你
想看我的游戏开发故事吗-它仍在进行中，
承诺-然后点击这里，发展的第一集。
非常感谢我的赞助人——他们是让你不回吃土的爹地。
像这样的长视频中的广告。你可以在 patreon.com 上帮助支持 GMTK.