zoukankan      html  css  js  c++  java
  • 使用Unity 3D开发iOS游戏入门教程

      (2013-09-04 16:59:38)
     

    Unity是目前最热门的游戏引擎之一,理由很充分——Unity具有强大的视觉编辑器,新手容易入门;功能丰富且强大;使用者多,价格便宜……

    如果你想自学Unity,那就从本教程开始吧!在本教程中,你将学到如何使用Unity制作一款简单的iOS游戏——以前没经验也可以!

    这款游戏的主要玩法就是:玩家要在某段时间内冲到终点,途中要避开障碍。

    在制作游戏的过程中,你将逐渐了解Unity的开发环境和流程,并且学习(或复习)到游戏设计的基本概念。

    本教程将分为三部分:

    在第一部分,你将学习使用Unity制作简单的角色操作机制。你还会了解如何在iOS上配置项目。

    第二部分,你将学习如何使用预制的控制器对象和一些在标准软件包中建立的脚本来改进角色的移动;如何使用Unity Remote调试;如何自定义场景。

    在第三部分,你将学习如何给项目中添加玩法和创建游戏设计概念,如预制件、计时器、菜单和声音等,以丰富游戏体验。

    准备好学习新技能了吗?那就开始吧!

    入门

    沿着这个系列的教程,你会慢慢熟悉Unity开发流程,包括以下四个基本组件:

    1、用Unity Editor中装配游戏场景

    2、用MonoDevelop Editor编写脚本

    3、用Unity Remote进行远程调试

    4、通过Unity Editor发布到你的iOS设备上

    在教程的第一部分,安装好Unity后,你将学习Editor界面的基本知识,然后创建一个项目,以便了解基本的游戏概念。

    到第一部分的结尾,你的游戏中应该有一个可以通过触摸触发器来移动的角色。你还要在Unity Editor中和你的iOS设备中测试你的项目。

    本教程使用的是第4版Unity,如果你没有的话,就先下载吧。

    Unity提供30天内免费的完整试用版Unity Pro。这个版本已经足够开发出一款能在iOS上运行的游戏了。Unity在Mac和Windows系统上都可用,但本教程使用的是Mac系统。

    注:Unity软件包相当大,接近1GB,下载可能需要一些时间,看你的网速了。所以如果你想马上制作本教程的游戏,那就提前下载好吧。

    创建项目

    下载、安装、启动Unity。如果这是你第一次启动Unity,那么软件会提示你激活版本。勾选激活免费30天试用版的Unity Pro,如下图所示:

    unity_license_activation(from raywenderlich)

    unity_license_activation(from raywenderlich)

    然后你必须创建一个Unity帐号(如果你没有的话),确认你的邮箱地址,然后通过Unity登录帐号。

    登录后,你会看到几个问题。移到列表的底部点击“Not Right Now”就可以全部跳过了。然后的最后,你就可以开始使用Unity了。

    现在你看到的是Unity的界面,其中已经有一个默认项目了。如下图所示:

    angrybots_sample(from raywenderlich)

    angrybots_sample(from raywenderlich)

    丢掉那个项目,从菜单中选择FileNew Project创建新项目。你会看到如下图所示的Project Wizard(项目向导):

    project_wizard(from raywenderlich)

    project_wizard(from raywenderlich)

    在Project Wizard上,点击Set然后选择一个存放项目的文件夹。输入“DashAndZag”作为项目的名称并点击Save,然后再点击Create Project(创建项目)(游戏邦注:你会看到对话框提示你是否保存之前打开的默认项目,没有必要的话就选择不保存。)

    注:Project Wizard包含一些标准程序包,你可以在初始项目制作时导入。不过现在还用不上。到本教程的后期,你可以导入任何你需要的程序包。

    Unity会关闭当前项目并重启新项目:

    initial_project(from raywenderlich)

    initial_project(from raywenderlich)

    新画布出来了!但那些控制键、窗口和选项是怎么回事?从哪入手?

    初识UI

    完全习惯Unity Editor的UI确实要花上一段时间,所以多看看吧。首先要看的是右上角的Layout(布局)选项。将视图变成Wide模式:

    change_layout(from raywenderlich)

    change_layout(from raywenderlich)

    以下是整个界面的分解图:

    initial_project_annotated(from raywenderlich)

    initial_project_annotated(from raywenderlich)

    Project View(项目视图):你的Unity包含一个Assets文件夹,它包含的内容就显示在这个视图里。你制作的脚本、场景、预制件等Unity对象就显示在这里。

    Hierarchy View(层级视图):包含当前场景中的GameObjects(游戏对象)。在Unity游戏中的所有物品都叫作一个GameObject;可以是从GameObject菜单中或导入的资源中制作出来的简单物品。你的初始项目只包含一个游戏对象:主摄像机。

    Toolbar(工具条):你可以通过toolbar在场景视图或游戏视图中操作对象、改变编辑器视图的显示方式,以及预览游戏。

    Scene View(场景视图):你可以在这个视图中放置游戏对象。场景视图的右上角处有一个Scene Gizmo(场景小座标),它显示了场景摄像头的当前方向。

    Game View(游戏视图):这是从游戏的摄像头看游戏的视图。点击工具条的上Play按钮就进入这个视图了。另外,也可以通过点击视图上方的选项卡在场景视图和游戏视图之间切换。

    Inspector(检查器): 可以通过Inspector观察游戏对象的细节,然后调整对象的属性如大小或位置等,或附加脚本调整对象的行为。

    使用工具条转换工具来调整游戏对象的位置和查看场景视图,会更舒服。所以我们要先熟悉工具条的使用。

    Hand工具的使用

    使用Hand工具(手工具)可以控制场景的视野。

    hand_tool(from raywenderlich)

    hand_tool(from raywenderlich)

    想下一下你自己落入一个3D场景中,里面有许多游戏对象;你能看见你面前的游戏对象,在你身后的则看不见。

    你可能希望能看到更多对象(缩放),或者从左边右边观察其他对象,或者转过身看到后面的对象。这时候,你就要使用手工具了。

    因为游戏对象会越来越多,为了在场景中布置对象,你会更加频繁地使用这个工具。

    手工具的使用方法是,首先选中它,然后从层级视图中选择主摄像机(这样你才能在场景视图中看到东西);然后在场景中拖曳画布。

    缩放视图时,可以使用鼠标滚轮或按键拖曳。按下选项同时拖曳可以旋转视图。点击右上方的场景小坐标的箭头之一,可以随时重置视图。

    Handtool(from raywenderlich)

    Handtool(from raywenderlich)

    多练习,熟悉一下如何使用这个工具缩放/旋转场景。

    因为你会经常在手工具和其他工具之间切换,所以最好能记住它的快捷键。各个工具的快捷键默认分别对应键盘上的Q(手)、W(移动)、E(旋转)和R(缩放)。

    注:手工具不能移动对象,它只能改变场景的视野,看起来就好像是对象在移动。多练习用手工具操作场景视图吧。

    Move工具的使用

    你可以使用Move工具(移动工具)在3D空间中移动游戏对象。

    move_tool(from raywenderlich)

    move_tool(from raywenderlich)

    选择主摄像机(游戏邦注:如果在场景视图中找不到,就在层级视图中双击它),然后点击移动工具。你应该会看到主摄像机处出现一个三个箭头的小座标。三个箭头就表示物品可以移动的方向。

    move_gizmo(from raywenderlich)

    move_gizmo(from raywenderlich)

    红箭头表示X轴,绿箭头表示Y轴,蓝箭头表示Z轴。选中对象后左击,然后朝小坐标内部(白线框)拖曳,就可以转换或移动游戏对象了。

    选择主摄像机,然后移动。看看检查器的转换组件。X、Y、Z值就表示游戏对象的位置。移动对象时,你可以看到这些值在变化。

    TransformComponent(from raywenderlich)

    TransformComponent(from raywenderlich)

    使用移动工具或输入XYZ值都可以改变游戏对象的位置。

    想把游戏对象朝某个方向移动时,只要选择小座标上的对应箭头,左击并拖曳就可以了。将游戏对象往X轴方向的移动,你会发现在检查器中,只有X值发生变化。请在Y轴和Z轴方向做相同的测试。

    inspector_after_x_move(from raywenderlich)

    inspector_after_x_move(from raywenderlich)

    现在,你已经知道如何移动场景视图,以及如何移动对象。干得不错!

    Rotate工具的使用

    你可以使用Rotate工具(旋转工具)在3D空间中旋转游戏对象。

    rotate_tool(from raywenderlich)

    rotate_tool(from raywenderlich)

    为了学习旋转工具的用法,我们应该更加详细地了解场景小座标。场景小座标就在场景视图的右上角,它有不同的模式。

    其中一个模式(见下图的右半边)是自由视图,你可以从3D透视图中察看场景。如果你想知道游戏在3D空间中的样子,这个模式是非常管用的。

    点击小座标下方的字就切换到等角视图(见下图的左半边)。旋转游戏对象时比较适合使用这个模式。

    scene_gizmo_perspective_isometric(from raywenderlich)

    scene_gizmo_perspective_isometric(from raywenderlich)

    另一个模式是X轴视图,也就是从X轴观察场景。如果你想沿着Y轴或Z轴移动游戏对象,同时保持X轴不变,就可以使用这个模式。

    scene_gizmo_x(from raywenderlich)

    scene_gizmo_x(from raywenderlich)

    类似的,还有Y轴视图和Z轴视图。原理同X轴视图。

    scene_gizmo_y(from raywenderlich)

    scene_gizmo_y(from raywenderlich)

    scene_gizmo_z(from raywenderlich)

    scene_gizmo_z(from raywenderlich)

    选择主摄像机,右击小座标选择“Free”就切换到自由透视模式了。

    change_to_free(from raywenderlich)

    change_to_free(from raywenderlich)

    点击小座标下的字可以在等角模式和透视模式之间切换。依次点击X、Y、Z轴模式,看看场景小座标和场景视图的变化。

    再次选择自由视图,然后测试旋转效果。在主摄像机仍然选中的情况下,点击旋转工具。你应该会看到如下图所示的三个不同颜色的圈,分别代表三个旋转轴向。

    rotate_gizmo(from raywenderlich)

    rotate_gizmo(from raywenderlich)

    左击主摄像机的中心,然后用鼠标拖曳。注意,当你旋转对象时,你会发现检查器的TransformRotation属性在变化。你也可以手动修改这些属性值。

    在旋转时点击主摄像机的中心,就是在所有轴向上做自由旋转。你还可以绕着一个轴旋转游戏对象,比如,点击红色圆圈选中它,然后拖曳对象,就是在X轴上旋转对象了。对绿色和蓝色圆圈做同样的测试。

    视野

    现在,你有必要学习一下局部视野和全局视野之间的差别。

    Global vs Local(from raywenderlich)

    Global vs Local(from raywenderlich)

    通过InspectorTransformRotation将主摄像机的旋转属性值重置为0,0,0,点击工具条上的“Local”按钮。然后选择移动工具,这样你就可以看到场景小座标了:

    global_local_aligned(from raywenderlich)

    global_local_aligned(from raywenderlich)

    注意,场景小座标和主摄像机的小座标是类似的,两个小座标的三个轴都分别代表相同的方向。现在,在检查器中设置TransformRotation的值为0,0,-20,就是沿着Z轴旋转主摄像机。

    global_local_after_z_rotation(from raywenderlich)

    global_local_after_z_rotation(from raywenderlich)

    注意,主摄像机的小座标现在是沿着Z轴旋转的——这就表示这个对象的这个轴处于它的本地坐标。这与场景小坐标是不同的,因为场景小坐标代表的是整个空间的方向。切换到全局视图,看看有何不同。

    在教程的后半部分,你将学习移动游戏对象和发现它与不同空间(局部或全局)的关系。

    看一下旋转后的主摄像机,如果你现在想在局部空间视图中把它朝X轴方向移动,最终结果会不同于在全局空间中把它朝X轴移动。记住这一点,以后会用到!

    再学习下面的内容以前,请再次将旋转值重置为0,0,0。

    Scale工具的使用

    你可以使用Scale工具(缩放工具)调整游戏对象的大小。

    ScaleTool(from raywenderlich)

    ScaleTool(from raywenderlich)

    与其他游戏对象操作工具一样,你只能在三个轴向收改变对象的大小。你也可以选择在InspectorTransform面板中修改属性值来调整对象的大小。

    选择主摄像机,注意,缩放小座标是由位于各轴末端的方形控点和中心的方形控点组成的。

    沿着所有轴自由缩放的方法就,选择对象中心的方块后保持左击,然后朝外移让对象变大,朝内移让对象变小。看一看InspectorTransformScale属性,你会发现当你缩放对象时,属性值会发生变化。

    ScaleComponent(from raywenderlich)

    ScaleComponent(from raywenderlich)

    选择代表X轴的红色控点,按下同时朝外移动,这样对象就变大了。你会发现在检查器的InspectorTransform面板上,只有X值改变了。在Y轴和Z轴上做同样的测试。

    在InspectorTransform面板中重置主摄像机的设置,输入如下值:

    Position(位置): 0,1,-10

    Rotation(旋转): 0,0,0

    Scale(缩放): 1,1,1

    结果如下图所示:

    transform_reset(from raywenderlich)

    transform_reset(from raywenderlich)

    本教程之后还将介绍其他编辑器界面的组件,包括使用Play按钮预览游戏,和使用Unity调试器。

    添加游戏对象

    你已经学习了基本概念,现在可以开始添加游戏对象了——摄像机已经寂寞好久了。

    目前摄像机面对的世界还是空荡荡的。添加一个表示角色的方块,再添加一个表示地面的平面(角色将在上面移动)。

    当你使用Unity设计游戏时,最好先思考游戏玩法,而不要考虑角色是怎么样的。这样你才能集中精神优化玩法和角色的移动方式,而不是分心思考角色的外观。

    另外值得注意的是,虽然Unity是一个非常好的游戏引擎,但你不应该用它制作图形资源。有许多工具非常适合制作图形部件,而Unity更擅长导入各种工具制作的资源。

    游戏开发的分工通常是这样的:游戏开发人员使用Unity开发玩法,同时,图像设计师使用他们最拿手的图像工具制作图像。当图形资源能够达到美化游戏的目的时,二者就可以融为一体了。

    选择Game ObjectCreate OtherPlane,添加一个表示地面的平面。

    这个新平面游戏对象会出现在层级视图中。在层级视图中选择平面,然后设置TransformPosition值为0,0,0。在检查器中将它的大小设置为50、1、50。你的场景应该接近下图:

    plane_after_transform_changed(from raywenderlich)

    plane_after_transform_changed(from raywenderlich)

    接下来是添加一个角色游戏对象,用方块表示。对啦,就是方块英雄!

    选择Game ObjectCreate OtherCube,这样层级视图中就出现一个方块游戏对象了。在层级视图中选中方块,然后在检查器中设置TransformPosition值为0,0,0。结果如下图所示:

    cube_after_transform_changed(from raywenderlich)

    cube_after_transform_changed(from raywenderlich)

    点击移动工具,然后在场景小座标上点击Y轴,将对象的移动限制在Y轴上。移动方块,使它位于平面(地面)之上。你还可以通过设置位置属性值为0,1,0达到相同的效果。

    现在我们迎来激动人心的时刻了——点击工具条上的Play按钮,预览你的游戏:

    play_tool(from raywenderlich)

    play_tool(from raywenderlich)

    你现在就是在用Unity Editor测试你的游戏。你应该换到游戏视图,结果如下图所示:

    play_result_darkness(from raywenderlich)

    play_result_darkness(from raywenderlich)

    你现在看到的是通过场景的主摄像机看到的游戏。你的角色,这个方块英雄就站在地面的远处——多么孤独而勇敢的存在啊!

    这个游戏还没有什么看点,因为你还没添加任何组件来调整角色的行为。并且,场景有点暗了。我们先来解决场景问题,也就是给场景添加一个光源。

    再次点击Play按钮,退出游戏预览。回到场景视图,添加光源:选择Game ObjectCreate OtherPoint Light。像平常一样,移动光源到合适的地方,然后进一步调整位置。在检查器中设置transform position属性值为0,0,0。

    现在调整光源的高度和亮度,以便看清场景。给你提示一下:使用移动工具,只沿着Y轴移动。

    高度调好后,就可以调亮度了:在Light组件中修改Range(范围)和Intensity(强度)设置。如果想看得更清楚一点,可以利用手工具来环转场景。

    有了足够的光之后,点一下Game选项卡(不是点击Play按钮)切换到游戏视图,你就可以看清场景了。来回移动光源,直到你可以看到角色。

    game_light_added(from raywenderlich)

    game_light_added(from raywenderlich)

    调整完后,点击Play按钮检查修改效果。当你切换到游戏视图时,你看到的结果应该如下图:

    game_light_added(from raywenderlich)

    game_light_added(from raywenderlich)

    恭喜你啦,你已经添加完教程现阶段要求的所有游戏对象了。记住,你应该先设计游戏玩法,然后再考虑图像。

    既然你已经做好一些有意义的东西了,那就将结果作为场景保存到项目中吧。项目可以由一个或多个场景组成。你可以把一个场景等同于游戏的一个关卡。

    在这个教程中,你可以将各个部分当作一个场景。这是一个仿造的游戏关卡的概念,但你可以重复利用一个场景的资源。

    保存场景的方法是选择FileSave Scene,然后在Save Scene的对话框中输入场景名称为Level_1。你的项目视图将更新成你刚做好的场景。

    DashAndZag-PC-Mac-Linux-Standalone(from raywenderlich)

    DashAndZag-PC-Mac-Linux-Standalone(from raywenderlich)

    事件函数

    游戏现在还是静态的。我们必须让它“活”起来。

    你可以通过制作自己的脚本或使用已打包的脚本,附加到相应的游戏对象上,从而让对像活动起来。例如,在本教程的第一部分,你做了一个“通过触摸移动角色”的脚本。你制作的这个脚本应该响应触摸事件,以改变方块英雄的位置和旋转情况。

    Unity支持的脚本语言有:JavaScript、C#和Boo。本教程使用的是JavaScript。Unity的脚本语言包括预定义的事件函数,你可以用它定义游戏对象的行为。

    最常用的事件函数是Update()。如果你在脚本中定义这个函数,每一帧它就会被调用一次。每一帧游戏显示就会绘制一次,所以Update函数就负责帧中的显示。

    游戏以每秒某数量的帧运行,记住,这个速率根据游戏运行的平台、其他应用占用的系统资源会有所不同。这意味着,你不应该把东西做得太密集,并且你不可能估计到那个速率到底是多少。

    另一个预定义事件函数FixedUpdate()不同于Update(),它是个固定的帧速。如果你的脚本定义了这个方式,它就会每隔一段固定的时间调用一次(相比之下,Update方式的每一次调用可能各不相同)。

    因为物理是非常讲究时间的,所以更适合使用FixedUpdate()函数处理任何物理相关的活动。例如,现在有一个使用物理的游戏对象,如刚体 (之后会学习到)。当这个对象与另一个对象相撞时,你必须计算作用到对象上的力量,这就要通过FixedUpdate()函数来实现。

    至于像改变对象的位置或旋转之类的操作,最好使用Update()函数。

    第三个实用的函数是Start(),在第一帧update发生以前调用。一般会把初始化放在这里。

    最后是Awake(),它在游戏开始时调用。也是放初始化代码的好地方。

    触摸

    我们来尝试一下。创建一个新脚本:选择AssetsCreateJavascript。这样,一个带默认名称“NewBehaviorScript”的新脚本就出现在你的项目视图中。点击脚本名称,重命名为“MoveSimple”。

    双击脚本打开它。它应该出现在附带的MonoDevelop编辑器里,包含以下代码:

    #pragma strict

    function Start () {

    }

    function Update () {

    }

    Start()和Update()函数已经帮你写好了,因为它们是常用的事件函数。如果你不需要Start()函数,那就去掉它。添加如下变量,它们是用来控制方块英雄的移动和旋转速度:

    var speed : float = 3.0;
    var rotateSpeed : float = 10.0;

    用如下代码替换空白的Update():

    function Update () {
    // Detect mouse left clicks
    if (Input.GetMouseButtonDown(0)) {
    // Check if the GameObject is clicked by casting a
    // Ray from the main camera to the touched position.
    var ray : Ray = Camera.main.ScreenPointToRay
    (Input.mousePosition);
    var hit : RaycastHit;
    // Cast a ray of distance 100, and check if this
    // collider is hit.
    if (collider.Raycast (ray, hit, 100.0)) {
    // Log a debug message
    Debug.Log(“Moving the target”);
    // Move the target forward
    transform.Translate(Vector3.forward * speed);
    // Rotate the target along the y-axis
    transform.Rotate(Vector3.up * rotateSpeed);
    } else {
    // Clear the debug message
    Debug.Log(“”);
    }
    }
    }

    保存修改。

    这份代码首先寻找鼠标点击事件,特别是鼠标左键的点击。当使用GetMouseButtonDown函数时,参数“0”表示左键单击,“1”表示右键单击,“2”表示中键单击。如果检测到单击事件,代码就会使主摄像机生成一道光束(想一下激光笔),移到鼠标点击的位置。

    collider.Raycast()函数会检测脚本中的碰撞器是否被光束击中。如果碰撞器被击中,collider.Raycast()就会返回“true”。这个函数调用有3个参数:

    1、你构建的光束

    2、RaycastHit对象,如果碰撞器被击中,还包含更多细节

    3、光束的长度参数

    假设结果是“true”,接下来的代码就会让角色向前移动,speed变量决定它的速度,rotateSpeed变量决定它的旋转。transform.Translate()函数将游戏对象按它的Vector3输入指定的方向和距离进行移动。

    Vector3是代表一个具有方向和长度或大小的对象的3D矢量。这个矢量可以分解成X、Y和Z组件。

    在代码中,你的方块英雄向前移动,距离增加器是根据speed变量计算的。Vector3.forward与Vector3(0,0,1)一样,速度增加器为3就是把方块按Vector3(0,0,3)移动。

    角色的移动方向与角色的本地座标有关。选择方块,然后使用移动工具,你看到的Z轴方向就是角色将前进的方向。

    transform.Rotate()函数会根据由Vector3输入指定的角度旋转游戏对象。Euler角就是用来描述游戏对象的方向的。Euler角Z度就是绕着Z轴旋转,Euler角X度就是绕着X轴旋转,Euler角Y度就是绕着Y轴旋转。

    在以上代码中,角色使用Vector3.up或Vector(0,1,0)作旋转动作,表示绕着Y轴移动。旋转角度按rotateSpeed增加。旋转与角色的本地座标有关。所以你的方块英雄将绕着它的Y轴旋转,你知道的,这与全局的Y轴是不一样的。

    注:真正的游戏绝对不会使用这类移动方式来控制角色。本教程是为了介绍关于移动游戏对象的基本概念才使用的。教程后面使用的将是更真实的移动方式。

    这份代码包含两个Debug.Log()语句。当游戏在运行时,log输出就会在Unity控制器中打印。在Unity Editor的底部,你可以看到最后一次的log输出。你还可以通过选择WindowConsole看到前一次log输出,或清除log记录。

    你应该时时注意Unity Editor底部的log输出,因为那就是显示脚本错误的地方。

    通过代码定义行为后,就可以把行为赋给游戏对象了。在层级视图中选择方块,然后选择ComponentScriptsMove Simple。这个步骤就是添加一个脚本行为给游戏对象,你也可以简单地选择你已经做好的脚本(名称为MoveSimple)。

    after_movesimple_added(from raywenderlich)

    after_movesimple_added(from raywenderlich)

    注意,检查器现在包含一个MoveSimple的脚本段落。你可以在检查器视图中看到你在脚本、speed和rotateSpeed中定义的公共变量。你现在可以只调整这些公共变量,而不必变更基础脚本。

    这意味着如果在场景中有两个角色带有MoveSimple脚本,那么只要增大InspectorMove SimpleSpeed的数值,就可以让其中一个角色移动得比另一个更快。

    点击Play预览游戏。点一下角色让它移动,注意检查器中的位置和旋转属性的变化。

    gameview_transform_moved(from raywenderlich)

    gameview_transform_moved(from raywenderlich)

    注意,位置从0,1,0变成0,1,3了——换句话说,根据速度增加器,角色在Z轴上移动了3。旋转也从1,1,1变成1,10,1,这是旋转速度增加器造成的结果。

    游戏在运行时,你可以在检查器中调整速度和旋转速度的变量。点击角色后,增加速度值,观察角色位置的变化。

    停止游戏,注意速度和旋转速度变量会恢复到初始值。在游戏运行中你做的任何调整都是暂时的。这有助于你尝试不同的数值,结束预览后回到初始设置。如果你决定改变设置,那就要在测试游戏以前进行。

    注意,当你运行游戏时,控制器记录正确的排错声明,这取决于角色是否被点击。

    摄像机的设置

    你应该已经注意到,当你移动方块英雄时,它就会离摄像机越来越远——感觉不太好。就像无微不至的父母照看刚学会走路的孩子,你希望让主摄像机在角色后面一直跟着。这是第三人称射击游戏的典型做法。

    选择AssetsImport PackageScripts,在Items to Import窗口中选择SmoothFollow脚本,然后点击Import:

    import_smoothfollow(from raywenderlich)

    import_smoothfollow(from raywenderlich)

    注:如果当你选择AssetsImport PackageScripts时没有看到导入选择的对话框,那么就试一下退出Unity后重新启动。这么做对我来说是有效的。

    在你的项目视图中应该可以看到新的Standard Assets文件夹。浏览它的下级文件夹可以看到SmoothFollow脚本。双击这个脚本,在MonoDevelop中打开,这样你就看到了代码的关键部分。

    var target : Transform;

    以上表示被追踪目标的公共变量。使用目标的转变信息来设置追踪者(主摄像机)的位置和旋转。

    追踪者的位置和旋转在LateUpdate()中设置,这是另一个预定义事件函数。这个函数是在Update()函数完成后调用的,一般你会把与发生在Update()中的操作有关的代码放在LateUpdate()中。

    当方块英雄的位置和旋转在MoveSimple脚本定义的Update()中改变了,摄像机就会在SmoothFollow脚本的LateUpdate()函数中更新它自己的位置和旋转。

    ..
    var wantedRotationAngle = target.eulerAngles.y; // 1
    var currentRotationAngle = transform.eulerAngles.y;
    ..
    currentRotationAngle = Mathf.LerpAngle (currentRotationAngle, wantedRotationAngle, rotationDamping * Time.deltaTime);
    ..
    var currentRotation = Quaternion.Euler (0, currentRotationAngle, 0);

    ..
    transform.position = target.position; // 2
    transform.position -= currentRotation * Vector3.forward * distance;
    transform.position.y = currentHeight;
    ..
    transform.LookAt (target); // 3

    以上代码段落有几个关键点:

    1、找到追踪者必须旋转的角度,以匹配目标的旋转。旋转是绕着Y轴进行的。

    2、追踪者在目标之后沿着Y轴移动,距离由距离变量决定。追踪者还根据目标的旋转绕着Y轴旋转。最后,确保追踪者在目标上方的高度固定。

    3、确保追踪者总是面向目标。

    现在将脚本赋给主摄像机。在层级视图中选择主摄像机,然后选择ComponentCamera ControlSmooth Follow:

    after_smoothfollow_added(from raywenderlich)

    after_smoothfollow_added(from raywenderlich)

    检查Smooth Follow的新段落是不是添加到主摄像机的检查器中。

    注意,Target变量现在还没赋值。你要把方块这个游戏对象指定给这个输入。在主摄像机仍然选中,Smooth Follow脚本在检查器中可见时,将方块游戏对象从层级视图中拖曳到Target变量。或者,你也可以点击Target值旁边的带小点的圆圈,从对象列 表中选择。

    smoothfollow_target_added(from raywenderlich)

    smoothfollow_target_added(from raywenderlich)

    现在,Target变量已经与方块(角色)连系起来了。

    点击Play按钮。你应该可以马上看到视野的变化,你现在是从给定的高度观察角色。点击角色移动它,观察摄像机如何追踪角色。注意主摄像机在检查器中的转变信息会随着方块英雄的移动而发生变化。

    停止游戏,保存后再进行下一步。

    在iOS上部署

    你一定等不及想看看游戏在iOS设备上的运行情况。好消息是,Unity Pro试用版允许你在iOS上测试你的项目!

    最好能在真正的设备上测试你的Unity项目。你总不希望仅在模拟器上测试吧,即使你可以做到。因为游戏开发严重依赖基础硬件,所以只有在真正的设备上测试,才能更容易发现性能问题。

    在iOS设备上测试以前,你必须是已注册的苹果应用开发者,具有设备部署能力。如果你不是,那么就花99美元注册一下吧。

    Unity可以把你的游戏建成X代码项目,使你可以在iOS设备上部署。选择FileBuild Settings:

    build_settings(from raywenderlich)

    build_settings(from raywenderlich)

    选择iOS平台,然后点击Switch Platform。再点击Build Settings对话框底部的Player Settings按钮。Unity Editor的检查器会显示Player Settings,你可以自定义你的iOS部署。请做以下调整:

    默认方向:横版向左

    player_settings_resolution(from raywenderlich)

    player_settings_resolution(from raywenderlich)

    其他设置

    捆绑标识符:这样你的应用才能顺序部署到设备上。

    SDK 版本: Device SDK

    瞄准的iOS版本:4.3

    player_settings_other(from raywenderlich)

    player_settings_other(from raywenderlich)

    返回到Build Settings对话框。在Scenes In Build窗口添加场景。因为你的项目只有一个场景,所以很容易添加。点击Add Current按钮就可以添加Level_1 scene了。

    build_settings_scene_added(from raywenderlich)

    build_settings_scene_added(from raywenderlich)

    点击Build按钮,启动创建进程。你可以指定某个地方保存X代码项目:在Save As中输入DashAndZag,然后选择Save。

    Unity现在就建立了一个项目,打开包含X代码版本的文件夹。这个项目的名称是Unity-iPhone.xcodeproj。

    打开X代码的项目,确保scheme设置成Unity-iPhone,选择你的设备,然后建立项目。

    xcode_scheme(from raywenderlich)

    xcode_scheme(from raywenderlich)

    运行应用。你首先会看到带Unity默认标志的画面。然后,你的游戏就出来了:

    game_ios(from raywenderlich)

    game_ios(from raywenderlich)

    触碰方块英雄,观察它的移动。你已经将你的第一个Unity项目部署到iOS上了!

    操作

    等等,事情还没完呢!

    如果你玩得更久一点,你会注意到一件奇怪的事。你可以通过一根手指触摸方块来移动它。但是,当你用两根手指放在离它等距离的地方时,方块也会移动。这是怎么回事?

    从MoveSimple脚本中调出这行代码:

    var ray : Ray = Camera.main.ScreenPointToRay
    (Input.mousePosition);

    这行代码就是让摄像机发出的光束移到触摸点。Input.mousePosition就表示触摸点。当项目在iOS设备上运行时,Input.mousePosition是按当前所有触摸的平均位置计算的,所以当你的手指那么放时,角色就移动了。

    你应该修复这个问题,以免困扰用户。你可以使用Unity中的触摸相关函数来检测触摸事件,以便找到更准确的触摸位置。

    开打MoveSimple脚本。在顶部(其他变量所在的地方)添加一个标记,表示你可以使用触摸输入:

    private var isTouchDevice : boolean = false;

    你可以在iOS和Unity Editor中测试。如果你只在iOS上测试,那么你就可以忽略触摸检查逻辑,只使用触摸相关的函数。

    下一步是添加一个Awake()函数,它会执行运行期检查,以检测游戏是否在iOS环境下运行。Awake()函数只调用一次,也就是在游戏载入的时候:

    function Awake() {
    if (Application.platform == RuntimePlatform.IPhonePlayer)
    isTouchDevice = true;
    else
    isTouchDevice = false;
    }

    Application.platform返回游戏正在运行的平台。RuntimePlatform.IPhonePlayer表示游戏正在iOS上运行。

    最后,将你的Update()函数作如下修改:

    function Update () {

    var clickDetected : boolean;
    var touchPosition : Vector3;

    // Detect click and calculate touch position
    if (isTouchDevice) {
    clickDetected = (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began);
    touchPosition = Input.GetTouch(0).position;
    } else {
    clickDetected = (Input.GetMouseButtonDown(0));
    touchPosition = Input.mousePosition;
    }

    // Detect clicks
    if (clickDetected) {
    // Check if the GameObject is clicked by casting a
    // Ray from the main camera to the touched position.
    var ray : Ray = Camera.main.ScreenPointToRay
    (touchPosition);
    var hit : RaycastHit;

    保存修改。

    你正在使用两个新本地变量,clickDetected和touchPosition,检测点击和保存点击位置。如果是在iOS上运行,那么就是通过查看触摸来检测点击。然后,根据第一次触摸的位置来计算点击位置。如果你不是在iOS上运行,那么逻辑如之前的一样。

    在重建项目以前,请关闭X代码项目。现在,从Build Settings对话框中重建。如果出现以关于Build文件夹已存在的警告,那就选择Replace。

    build_replace_warning(from raywenderlich)

    build_replace_warning(from raywenderlich)

    X代码项目重建好后,在iOS设备上运行游戏。检验是否能移动方块英雄,以及用两根手指移动它的问题是否解决。

    结尾

    恭喜啦,你已经初步掌握Unity的基本概念和在iOS上部署!

    在本教程的下一部分中,你将学习如何改进方块英雄的移动和完善场景。另外还要做一点调试工作!

    这是使用Unity 3D创造iOS游戏教程系列的第二部分。

    在该系列的第一部分我们通过创建一个非常简单的项目并将其用于iOS设备上而了解了Unity的基本要素。

    而今天我们将添加一些函数去丰富这一项目,即包括更有趣的玩家移动和更棒的游戏场景。

    让我们开始吧:场景的改变

    首先打开我们在第一部分教程中所创建的Unity项目。

    今天的教程将基于一个全新的场景,从而让玩家可以在之后轻松地使用之前的场景作为参考。

    选择FileSave Scene as…并将新场景命名为Level_2。新的Level_2场景将出现在项目视图中。

    之前场景中的所有资产(游戏邦注:包括脚本)都能够用于新场景中。在你可以改变GameObjects在Level_2场景中的呈现而不会影响到Level_1场景中相同的对象。

    玩家的移动

    首先我们需要强化玩家的移动。现在我们的Heroic Cube始终都是向前移动,并向右旋转。这种移动并不顺畅,也不有趣。

    角色控制器是Unity的组件,我们能够将其附加到GameObject去创造更加真实的移动。我们可以基于脚本函数去操纵角色控制器。

    举个例子来说吧,我们可以调用预先定义的SimpleMove函数去移动角色。在Vector3中SimpleMove函数代表执行移动的速度。角色的移动会自动算上重力,从而让它能够爬上斜坡也能爬下楼梯。

    同时角色还能滑过各种障碍无需你去编写代码提示他们。

    为了更好地理解这一点,我们可以在Hierarchy View中选择Cube GameObject,并选择ComponentPhysicsCharacter Controller。你将看到一个对话框在询问是否用CharacterController替换Box Collider。

    replace(from raywenderlich)

    replace(from raywenderlich)

    点击替换。

    在Inspector中你可以看到一个有关角色控制器的新组建。你需要创造一个全新的脚本去控制玩家的移动,从而让你无需再将MoveSimple脚本附加到Heroic Cube上。

    move simple(from raywenderlich)

    move simple(from raywenderlich)

    在Inspector点击MoveSimple脚本组件右边的齿轮图标,并选择移动组件。脚本将不再出现于Inspector。

    reset(from raywenderlich)

    reset(from raywenderlich)

    通过选择AssetsCreateJavaScript创建一个新的JavaScript资产,并将其命名为MoveAround。在MonoDevelop Editor双击打开新脚本。选择存根函数并添加如下代码:

    var speed : float = 3.0;
    var rotateSpeed : float = 3.0;

    function Update () {
    var controller : CharacterController = GetComponent(CharacterController);

    // Rotate around y – axis
    transform.Rotate(0, Input.GetAxis (“Horizontal”) * rotateSpeed, 0);

    // Move forward / backward
    var forward : Vector3 = transform.TransformDirection(Vector3.forward);
    var curSpeed : float = speed * Input.GetAxis (“Vertical”);
    controller.SimpleMove(forward * curSpeed);
    }

    @script RequireComponent(CharacterController)

    保存你所做出的改变。

    脚本的最后一行内容明确了这一脚本只能附加到GameObject(游戏邦注:拥有一个角色控制器组件)。

    Update()函数拥有角色控制器组件的句柄。然后它将围绕着y轴(基于代表着左/右移动的输入内容)进行旋转。左/右移动是默认地由左/右箭头和A/D键所控制。

    Input.GetAxis()值的范围是从-1到+1。负值代表逆时针方向,而正值则代表顺时针方向。

    基于来自Input.GetAxis(“Vertical”)的输入内容,SimpleMove函数能够向前或向后移动角色。这一输入内容是由向上 /向下箭头或W/S键所触发的。就像水平输入一样,Input.GetAxis()值的范围是从-1到+1,负值和正值将分别触发向后和向前的移动。我们 将使用倍速器去控制和旋转角度或移动距离。

    将新脚本附加到Heroic Cube。

    当你完成了这些设置后,你便能在玩家的Inspector上看到新的Move Around脚本组件。

    character controller(from raywenderlich)

    character controller(from raywenderlich)

    为什么不使用Unity Editor去旋转Heroic Cube?点击游戏按键并感受左右旋转,向前并向后移动的乐趣吧!

    使用左/右/上/下箭头进行移动,同时测试W/A/S/D按键。多亏了角色控制器组件和脚本,如今玩家的移动变得更加顺畅了。

    使用Unity Remote进行调试

    你可能会注意到,在最后的教程中基于iOS设备测试项目有点麻烦。导出到Xcode项目与在设备上创建和运行过程需要我们花费一定的时间,而这些都包含在开发过程中。

    不过也存在一些更有效的方法。最快的调试方法便是使用名为Unity Remote的应用。这一应用让你能够略过多个步骤,如面向iOS创建项目,启动Xcode并在你的设备上运行。

    基于Unity Remote iOS应用,我们可以将自己的iOS设备与Unity Editor连接在一起,并从iOS设备上去控制Unity Editor。这能让你在Unity Editor上快速调试游戏。

    在App Store上购买Unity Remote是免费的。

    注:如果要使用Unity Remote,你的iOS设备和计算机必须使用同一个Wi-Fi网络。除此之外,Unity Editor窗口还必须保持前台操作。如果你将Unity Editor窗口转向后台,你便会收到“等待游戏视图。按压‘播放’”的信息。

    在Unity Editor上点击“播放”。在你的iOS设备上启动Unity Remote应用。从出现的列表中选择你的计算机。如果你的计算机并未出现在列表上,那就在Unity Remote的IP设置上输入计算机的IP地址。

    list(from raywenderlich)

    list(from raywenderlich)

    当Unity Remote与你的计算机匹配在一起时,你的iOS设备便能够在Unity Editor上控制游戏的运行了。虽然设备显示器的分辨率不是最理想的,但这却是创建游戏原型的快速方法。当然,你也想偶尔面向iOS创建项目并通过 Xcode发行,所以你可以基于更加现实的情境去测试游戏。

    基于iOS测试游戏。你会发现,因为在iOS中并不存在向左/右/上/下输入,所以你不能在设备上移动玩家(游戏邦注:但是你却可以在Mac上进行控制)。接下来你将执行一些操纵杆控制去解决这一问题。

    双重操纵杆,双重乐趣

    幸运的是,Unity的标准资产包(包含资产)能够执行操纵杆函数。我们需要选择AssetsImport PackageStandard Assets (Mobile)从资产包中导入相关道具(如下所示)。

    importing package(from raywenderlich)

    importing package(from raywenderlich)

    选择与操纵杆函数相关的指示道具。点击Import。在完成导入后,项目视图将包含名为Standard Assets (Mobile)的新文件夹。在带有导入操纵杆脚本的Untiy 4中你将看到如下警告:

    warning(from raywenderlich)

    warning(from raywenderlich)

    为了解决这一问题,让我们双击警告内容。如此便会打开操纵杆脚本文件。将鼠标放在警告行上:

    gameObject.active = false;

    修改声明为:

    gameObject.SetActive(false);

    现在当你回到Unity Editor时,你会发现警告已经消失了。

    首先在场景上添加Dual Joysticks预制件。预制件可以再次利用,并总是被定制为GameObject。我们可以从项目视图拖出预制件或经由脚本将其添加到场景中。例如,我们可以先创建一个代表玩家的预制件,并添加多个玩家到场景中。

    在你的项目视图中打开Standard Assets (Mobile)Prefabs文件夹。将Dual Joysticks预制件拖到层次结构视图中。

    hierarchy view(from raywenderlich)

    hierarchy view(from raywenderlich)

    我们可以点击游戏标签并进入游戏视图看到操纵杆:

    game view(from raywenderlich)

    game view(from raywenderlich)

    让我们回到场景视图中。

    在层次结构视图中点击Dual Joysticks旁边的三角形。我们可以注意到Dual Joysticks GameObject包含了两个孩子GameObjects。

    这个过程就像是“养育”过程,能够用于设置操纵杆父母/孩子GameObject之间的关系。如果你想要创造一个复合的GameObject(拥有 连接GameObject),那么“养育”过程便非常有帮助。举个例子来说吧,如果你想要左右操纵杆同时发挥作用,那么当它们都被当成单一对象时,你便能 够通过脚本轻松实现这一过程了。

    选择LeftJoystick GameObject,并注意到它附加了操纵杆脚本组件,选择RightJoystick GameObject进行同样的核查。

    操纵杆可以查出GUI Texture上的碰触事件,并基于某些限制条件去改变它的位置。例如,我们只能在特定范围内移动图像。脚本将确保位置输出的规范化,即保持在-1至+1的范围内。如此我们便能在iOS环境中使用操纵杆去替代Input.GetAxis()。

    这时候你已经在场景中放置了2个操纵杆,但是它们却不能连接到Heroic Cube去触发输入内容。我们将修改脚本去分配操纵杆的任务,即右边操纵杆能够旋转玩家,而左边操纵杆将前后移动玩家。

    修改MoveAround脚本去处理操纵杆的输入内容。打开脚本进行编辑,并添加两个代表移动和旋转操纵杆的公共变量:

    var moveJoystick : Joystick;
    var rotateJoystick : Joystick;

    然后添加一个新函数,即面向操纵杆的位置并回到输出范围(在-1至+1之间)。将其添加到 Update()函数之后:

    function joyStickInput (joystick : Joystick) {
    var absJoyPos = Vector2 (Mathf.Abs(joystick.position.x),
    Mathf.Abs(joystick.position.y));
    var xDirection = (joystick.position.x > 0) ? 1 : -1;
    var yDirection = (joystick.position.y > 0) ? 1 : -1;
    return ( ( absJoyPos.x > absJoyPos.y) ? absJoyPos.x * xDirection : absJoyPos.y * yDirection);
    }

    操纵杆输入是一个Vector2输入,拥有x和y组件。我们可以使用更大的x或y绝对值去设置输出内容。我们还可以将操纵的方向作为倍增器去指示负值或正值。结果便是输出值的范围也是在-1至+1之间。

    修改Update()函数去处理来自Unity Editor或iOS设备的输入内容:

    function Update () {
    var controller : CharacterController = GetComponent(CharacterController);

    // Rotate around y – axis
    var rotatePos = Input.GetAxis (“Horizontal”) ?
    Input.GetAxis (“Horizontal”) : joyStickInput(rotateJoystick);
    transform.Rotate(0, rotatePos * rotateSpeed, 0);

    // Move forward / backward
    var forward = transform.TransformDirection(Vector3.forward);
    var movePos = Input.GetAxis (“Vertical”) ?
    Input.GetAxis (“Vertical”) : joyStickInput(moveJoystick);
    var curSpeed = speed * movePos;
    controller.SimpleMove(forward * curSpeed);
    }

    变换的Rotate()函数将接受左/右箭头(或A/D按键)或旋转操纵杆的输入。角色控制器的SimpleMove函数将获得来自上/下箭头(或W/S按键)或左操纵杆的输入。

    保存改变后的脚本。选择玩家GameObject(立方体)并注意Move Around脚本组件拥有两个关于移动操纵杆和旋转操纵杆的全新公共变量。

    move around(from raywenderlich)

    move around(from raywenderlich)

    目前这些变量并未得到分配。我们仍需要选中玩家GameObject,并将LeftJoystick GameObject拖到Move Joystick变量。将RightJoystick GameObject拖到Rotate Joystick变量。(或者我们也可以使用每个变量旁边的选择器。)

    move around(from raywenderlich)

    move around(from raywenderlich)

    点击Unity Editor的“播放”,并在我们的iOS设备上开始进行Unity Remote。

    测试操纵杆函数并核实我们是否能使用左操纵杆向前或向后移动,并使用右操纵杆向左和向右移动。当我们在iOS设备上移动操纵杆时,我们也应该能够看到操纵杆在Unity Editor上移动。

    unity editor(from raywenderlich)

    unity editor(from raywenderlich)

    注:也许你看到的操纵杆图像如图所示。但是不要担心,在真正的设备上出现的操纵杆并不是这样。

    清晰明亮的天空盒

    如今我们已经完善了Heroic Cube的移动,接下来是否该完善场景呢?到目前为止玩家一直待在一个阴暗的世界。我们应该想办法去美化游戏环境。

    幸运的是这并不困难。Unity包含标准资产包中的资产,我们可以使用这些资产去创造天空,添加植被并改变地形的高度。

    在这一阶段你将利用天空盒组件去添加天空。天空盒是一个较大的立方体,能够附加到游戏的Main Camera上。当你添加了组件时,你便能够添加材料去装饰它,并经过渲染而创造出天空的效果。

    选择AssetsImport PackageSkyboxes开始输入过程。选择Sunny2 Skybox.mat,然后点击Import。

    importing package(from raywenderlich)

    importing package(from raywenderlich)

    在完成输入后,你的项目视图将拥有一个全新的Standard AssetsSkyboxes文件夹,即包含最新输入的资产。

    project(from raywenderlich)

    project(from raywenderlich)

    选择Main Camera对象,然后选择ComponentRenderingSkybox。

    smooth follow(from raywenderlich)

    smooth follow(from raywenderlich)

    新组件将出现在Inspector。我们可以注意到新组件包含一个代表材料的变量。在此你所输入的天空盒材料便能发挥作用了。

    点击Custom Skybox变量旁边小小的圆形图标。这时候将弹出Select Material对话框,其中列出了一些你的项目可以使用的材料资产。

    select material(from raywenderlich)

    select material(from raywenderlich)

    现在场景视图将发生改变,即呈现出我们所添加的明亮天空。我们可以点击游戏标签去观看游戏视图中的天空,或编辑“播放”按键。

    smooth follw(from raywenderlich)

    smooth follw(from raywenderlich)

    太阳也出现了!尽管有时候会出现阴天,但是我们也可以添加一些绿色植物为游戏世界增添色彩。

    scene(from raywenderlich)

    scene(from raywenderlich)

    不要忘记植物

    让我们回到场景视图。我们可以添加地形GameObject而创造植被,然后添加材料(如草地)和对象(如树木)。

    这时候我们将不再需要平面GameObject,因为地形将负责铺设层面。右击层次结构视图的平面GameObject,然后选择删除。

    选择AssetsImport PackageTerrain Assets。

    importing package(from raywenderlich)

    importing package(from raywenderlich)

    点击Import去输入所有选择。这时候地形资产将出现在标准资产文件夹中。我们可以使用已经输入的资产去修改地形GameObject的行为。

    选择TerrainCreate Terrain将地形GameObject添加到场景中。选择地形对象并使用Inspector将其参数域改为-1000,0,-1000。这将最大限度地延伸地形,从而提供给玩家足够广的移动空间。

    terrain(from raywenderlich)

    terrain(from raywenderlich)

    Terrain (Script)所包含的工具能够帮助我们装饰地形:

    terrain script(from raywenderlich)

    terrain script(from raywenderlich)

    选择绘画工具中的画笔:

    paint tool(from raywenderlich)

    paint tool(from raywenderlich)

    我们将能用草地去装饰地形。点击Edit TexturesAdd Texture:

    grass(from raywenderlich)

    grass(from raywenderlich)

    在Add Terrain Texture对话框的纹理区域中点击“选择”,选择Grass (Hill)纹理,关闭弹出的Select Texture2D,然后选择“添加”:

    terrain script(from raywenderlich)

    terrain script(from raywenderlich)

    点击场景然后开始绘制草地。检查原图,然后点击游戏标签:

    grass scene(from raywenderlich)

    grass scene(from raywenderlich)

    如果只是进行景观美化便非常简单!

    回到场景视图–现在我们便能够种植一些棕榈树了。在Terrain (Script)中选择“种树”工具:

    terrain script(from raywenderlich)

    terrain script(from raywenderlich)

    点击Edit TreesAdd Tree:

    add tree(from raywenderlich)

    add tree(from raywenderlich)

    在添加树的对话框中点击树旁边的圆形图标,选择Palm GameObject,然后点击“添加”:

    terrain script(from raywenderlich)

    terrain script(from raywenderlich)

    将笔触大小设置为20:

    brush size(from raywenderlich)

    brush size(from raywenderlich)

    然后我们便可以点击适当的位置去添加树了。提示:放大场景并着眼于我们想要种树的位置。在添加树的过程中尝试着调整电光源的范围和高度去浏览更多场景。

    point light range(from raywenderlich)

    point light range(from raywenderlich)

    完成这些设置后你的场景将如下图所示:

    scene textured(from raywenderlich)

    scene textured(from raywenderlich)

    已经很棒了!渐渐有了真实世界的雏形了。但是地形看起来还是很平坦。

    这时候我们可以在地形中添加一些高度变量。在Terrain (Script)中选择最左边的工具去提高或降低地形的高度:

    terrain script(from raywenderlich)

    terrain script(from raywenderlich)

    将笔触大小设置为20,然后在场景视图中围绕着玩家点击2至3个点(不要靠太近)以变化地形的高度。

    scene textured(from raywenderlich)

    scene textured(from raywenderlich)

    预览游戏并在场景中移动。适当调整场景中树的数量,并记得在做出任何改变之前暂停游戏。

    tree(from raywenderlich)

    tree(from raywenderlich)

    保存场景。

    使用Unity Remote去测试项目,当你感到满意时便能够将其作为一个Xcode项目。选择FileBuild Settings,在构建设置的对话框中点击Add Current去添加Level_2场景。新场景将出现在Scenes In Build列表上。这是取消Level_1场景的选定而至创建Level_2。

    settings(from raywenderlich)

    settings(from raywenderlich)

    点击建造,必要的话选择替换之前的项目,然后启动Xcode项目。在我们的iOS设备上调配并测试。

    iOS device(from raywenderlich)

    iOS device(from raywenderlich)

    在本系列第一部分,你已经掌握了基本的Unity工具用法,创造了一款含简单的玩家控制机制的游戏,并且知晓如何在iOS平台配置项目。

    在本系列第二部分,你加快了游戏中Heroic Cube的移动,加入天空、草地、树木和多变地形,使游戏世界呈现了一些生命力。

    在第三部分中,你将给项目添加游戏玩法。这里不是简单地在场景中四处移动,而是让你的Heroic Cube在特定时间内撞击到达终点线。

    为了给玩家制造一些挑战,我们要在Cube奔向终点的过程中设置一些障碍。为此我们要添加一个定时器,并在获胜时添加欢呼声,失败时则是死寂般的沉默。

    开始

    首先,将Level_2场景保存成新场景并命名为Level_3。在第三部分教程中,你将针对新场景改变原有内容。

    你可以用一个粗线将两个标杆连接起来创造终点线,这样玩家可以清楚地看到目的地。终点线将包括一个无形的墙,如果Heroic Cube过线了就会触发相应的结果。

    选择GameObjectCreate Empty创造一个代表终点线的新对象。这是一个GameObject的母对象,其子对象将包括标杆、线条和墙体。

    将这一对象通过Inspector仪表盘或者右击对象,选择“重命名”将其重命名为Finish Line。将转变位置设为0,0,0。

    要创造第一个标杆,选择GameObjectCreate OtherCylinder,将其重命名为Post1。将转变范围设为1,3,1。将转变位置设为-10,0,20令其呈现在玩家左前方。使用Move Tool,调整Y位置,让圆柱体底部就略低于地面。

    提示:从Z轴查看场景有助于进行调整。

    post1_after_height_adjusted(from raywenderlich)

    post1_after_height_adjusted(from raywenderlich)

    拖拽Post1 GameObject并将其放置于Finish Line GameObject,让后者成为Post1的母体。

    post1_parented(from raywenderlich)

    post1_parented(from raywenderlich)

    要创造第二个标杆,选择Post1,右击并选择“复制”。再右击一次并选择“粘贴”。将这个新的GameObject从Post1重命名为Post2。使用Move Tool,调整X位置以便标杆立于玩家右侧。

    提示:从Y轴查看场景以便做出调整。或者,将转变X位置设为10也可以。

    post2_after_xposition_adjusted(from raywenderlich)

    post2_after_xposition_adjusted(from raywenderlich)

    下一步,创造用于检测Cube是否穿过终点线的墙体。选择GameObjectCreate OtherCube,将其重命名为Goal。将转变范围设为24,10,0.5。将原转变位置设为0,2,0。

    goal_scale_adjusted(from raywenderlich)

    goal_scale_adjusted(from raywenderlich)

    将墙体移动到两个标杆之后。如果必要的话,调整X范围值,以便墙体从一个标杆延伸到另一个标杆。

    goal_scale_position_adjusted(from raywenderlich)

    goal_scale_position_adjusted(from raywenderlich)

    现在墙体仍在选择状态,打开InspectorBox Collider组件,查看Is Trigger数值。取消选中Mesh Renderer组件以便让墙体隐形。

    boxcollider_istrigger_meshoff(from raywenderlich)

    boxcollider_istrigger_meshoff(from raywenderlich)

    拖拽Goal GameObject,使其位于Finish Line GameObject之下,成为后者的子对象。

    goal_parented(from raywenderlich)

    goal_parented(from raywenderlich)

    连接节点

    下一步就是创造连接标杆的线条,以便玩家清楚看到终点线。你将通过脚本从一个标杆到另一个标杆绘制出线段。

    选择AssetCreateJavaScript创造新脚本并将其命名为FinishLineRender。在Project View中双击打开脚本,删除其中的隔离功能,添加以下代码:

    // The finish line posts
    var post1 : Transform;
    var post2 : Transform;

    var lineColor : Color = Color.green;

    function Start () {
    // Set the visual for the finish line posts
    var lineRenderer : LineRenderer = gameObject.AddComponent(LineRenderer);
    lineRenderer.SetPosition(0, post1.position);
    lineRenderer.SetPosition(1, post2.position);
    lineRenderer.material = new Material (Shader.Find(“Particles/Additive”));
    lineRenderer.SetColors(lineColor, lineColor);
    }

    LineRenderer类允许玩家在3D环境中绘制线条。通过一排的节点,你可以使用Line Renderer组件(ComponentsEffectsLine Renderer)画出直线。

    你可以将Line Renderer组件添加到Finish Line对象,并为Post1和Post2的转变位置进行硬编程,但通过代码创建Line Renderer会更轻松些。这就是你现在的操作。

    你要在Start()函数中画出线条,这只要一次就成。首先你要添加LineRenderer脚本界面组件,然后从变量输入的值中为线条设置第一和第二个点。设置渲染器中的材料。

    最后,设置线条起点和终点的颜色。要让线条颜色值设为公共状态,以便过后进行调整。

    将FinishLineRender脚本组件附于Finish Line GameObject。

    提示:你可以通过选择Finish Line对象然后轻敲Inspector中的Add Componet按钮为GameObject添加脚本。此时会出现一个检索框——只需简单地输入“FinishLineRender”一词的头几个字母,就会自动显示脚本内容。

    finishline_script_added(from raywenderlich)

    finishline_script_added(from raywenderlich)

    分别将Post1和Post2 GameObject分配到Post 1和Post 2变量。

    finishline_vars_assigned(from raywenderlich)

    finishline_vars_assigned(from raywenderlich)

    在Unity Edito中预览游戏。你会看到两个标杆以及一条绿线所组成的终点线。

    gameview_finishline 1(from raywenderlich)

    gameview_finishline 1(from raywenderlich)

    下一步代将创造一个能够检测超过终点线这一行为的新脚本,并将这一新脚本粘附到Goal GameObject中。选择AssetsCreateJavaScript并将脚本命名为FinishLineDetection。

    打开新脚本并删掉冗余的函数,添加以下代码:

    function OnTriggerEnter(other : Collider) {

    if (other.gameObject.tag == “Player”)
    {
    Debug.Log(“You made it!!!”);
    }
    }
    @script RequireComponent(Collider)

    在另一个碰撞器进入GameOject时就调用OnTriggerEnter()函数。要将这个GameObject设置为触发器(正如Goal对象的设置一样)。

    玩家GameObject有一个称为角色控制器组件的碰撞器。所以当玩家冲过Goal GameOject时,就会触发OnTriggerEnter()事件。

    你要在你的代码中,查看GameObject是否进入了一个标签名为“玩家”的对象。如果是这样,就说明Heroic Cube穿过了终点线。

    将FinishLineDetection粘附到Goal GameObject。

    提示:在Hierarchy View中选择的Goal对象,从Project View中将FinishLineDetection脚本拖拽到InspectorAdd Component按钮,以将该脚本组件粘附到GameObject。

    在你拖拽玩家之前,为玩家GameObject命名(而不是简单的旧名“cube”)。为了保持一致,将Cube GameObject重命名为Player。

    cube_renameto_player(from raywenderlich)

    cube_renameto_player(from raywenderlich)

    现在,为玩家对象添加一个标签,以便终点线检测逻辑生效。在Inspector中下拉标签选项并选中Player。

    player_tag_select(from raywenderlich)

    player_tag_select(from raywenderlich)

    player_tagged(from raywenderlich)

    player_tagged(from raywenderlich)

    玩家是预建标签之一。之后,你将创建自己的标签以便鉴定游戏中所有的敌人。

    点击Play并将Heroic Cube移到终点线之后。你就会看到“你成功了!!!”这一记录信息,你就知道玩家此时已经穿过终点线。

    gameview_finishline(from raywenderlich)

    gameview_finishline(from raywenderlich)

    没错,Heroic Cube能够到达目标并赢得游戏,但你不能让它太轻松实现。你还得为游戏添加两层复杂性:一个基于时间的挑战和多个障碍。首先,让我们添加障碍。

    创建发射器

    创建一个空白的GameObject,并将其命名为Launcher,代表将阻止Heroic Cube前进步伐的障碍发射中心。

    在Z轴方向上,使用Move Tool将发射器对象放置于玩家和终点线之间,并且位于玩家之上。你可以将转变位置设为0,12,8,并根据需要进行调整。

    launcher_adjusted(from raywenderlich)

    launcher_adjusted(from raywenderlich)

    发射器的主要存在理由就是发射障碍,所以你得给它提供一些发射物。

    在Unity中创造的弹药一般是先设计一些GameObject,然后创造一些能够在游戏玩法场景中实例化的预制件。你将创建一个Obstacle GameObject,将其转变为预制件,然后让发射器将其射向倒霉的玩家。

    创建障碍

    创建一个方形的GameObject,并将其命名为Obstacle。把转变范围设为2,2,2,让它比玩家更大并更具威慑感。这就是那些将扮演坏蛋的立方体。

    让这些障碍看起来更有趣一些,而不只是默认的灰色物质。为匹配完整的样本,首先要从角色控制器包中输出一个材料:选择AssetsImport PackageCharacter Controller,然后选中constructor_done材料以及在下图中显示的相关纹理,最后点击Import。

    obstacle_material_import(from raywenderlich)

    obstacle_material_import(from raywenderlich)

    新材料会显示在你的Project View中。

    obstacle_material_after_import(from raywenderlich)

    obstacle_material_after_import(from raywenderlich)

    选择Obstacle GameObject。通过调整InspectorMesh RendererMaterialsElement 0属性改变渲染材料。点击靠近属性的圆形图标,产生“材料选择”对话框。

    obstacle_select_material(from raywenderlich)

    obstacle_select_material(from raywenderlich)

    选择你刚输出的constructor_done材料,关闭“材料选择”对话框。

    obstacle_after_material_added(from raywenderlich)

    obstacle_after_material_added(from raywenderlich)

    现在你必须为Obstacle GameObject贴标签,以便开始新游戏时清除场景中的Obstacle实例。

    为此,创建一个名为Enemy的新标签。点击InspectorTagAdd Tag。TagManager将显示在右侧栏。点击靠近标签的三角形来扩展标签阵列。将Element 3的值设为Enemy。

    add_enemy_tag(from raywenderlich)

    add_enemy_tag(from raywenderlich)

    选择Obstacle GameObject,并为这一对象贴上新的Enemy标签。

    obstacle_tagged(from raywenderlich)

    obstacle_tagged(from raywenderlich)

    当Obstacle被具体化时,你将添加的代码则需要将一个刚体组件粘附到障碍上。这要先添加一个刚体。选择ComponentPhysicsRigidbody(游戏邦注:Obstacle仍在选中状态):

    rigidbody_added(from raywenderlich)

    rigidbody_added(from raywenderlich)

    点击Project View中的Assets文件夹。选择AssetsCreatePrefab,为你的障碍物创建一个预制件。此时Project View会显示一个空白的预制件。将其命名为障碍。

    create_prefab(from raywenderlich)

    create_prefab(from raywenderlich)

    注:如果你的资产图标大于以上截图所示情况,并且不知该如何查看资产列表中的物品,只需向左下滑资产列表就能看到。

    将Obstacle GameObject拖拽到这个新预制件中。

    obstacle_prefab(from raywenderlich)

    obstacle_prefab(from raywenderlich)

    预制件变成蓝色表明它已经被指定。

    现在你已经创建了一个可作为重用资产的预制件,在这个场景中已经不再需要这个预制件。发射器将负责在必要的时候具体化一个Obstacle实例。在Hierarchy View中,选择Obstacle GameObject,右击并选择“删除”。

    释放障碍

    下一步,通过脚本创造逻辑以发射障碍,完成这一过程。

    创造一个新脚本资产并将其命名为ObstacleLauncher。

    提示:你可以在Project View中右击并选择CreateJavaScript以创建一个脚本。

    打开新脚本,用以下代码替换原来的函数:

    var projectile : Rigidbody;
    var speed = 5;
    var maxObstacles = 2;
    var launchInterval : float = 5.0;
    var target : Transform;

    private var nextLaunch : float = 0.0;
    private var numObstaclesLaunched = 0;

    function Start () {
    if (target == null) {
    // Find the player transform
    target = GameObject.FindGameObjectWithTag(“Player”).transform;
    }
    }

    function Update () {
    if ((numObstaclesLaunched < maxObstacles) && (Time.time > nextLaunch)) {
    // Set up the next launch time
    nextLaunch = Time.time + launchInterval;

    // Set up for launch direction
    var hit : RaycastHit;
    var ray : Ray;
    var hitDistance : float;

    // Instantiate the projectile
    var instantiatedProjectile : Rigidbody = Instantiate(projectile, transform.position, transform.rotation);

    // Simple block, try to get in front of the player
    instantiatedProjectile.velocity = target.TransformDirection(Vector3.forward * speed);

    // Increment the launch count
    numObstaclesLaunched++;
    }
    }

    这个发射器的编码将用于面向玩家发射特定数量的障碍。因此它需要一些可代表玩家的输入。在此之前,将GameObject分配到脚本时,你可以使用Editor将GameObject拖拽到脚本变量中。也可以通过Start()函数用另一种方法实现这一做法。

    在Start()中,我们可以检查是否已无分配目标。如果没有找到目标,代码就会查询含有Player标签的GameObject,并将这个GameObject分配到目标变量。

    GameObject.FindGameObjectWithTag()函数调用是一个棘手的调用,因为它需要浏览所有的GameObject。所以你得在Start()中就调用这个函数以免将其引入Update()这个会重复多次调用的函数。

    Update()会在代码中首先查看发射器是否发送了最大限量的障碍。如果没有,它还会查看是否已经超过时间间隔。这可以避免在短暂时间内发射过多障碍。

    如果是时候发射另一个障碍,那么Obstacle预制件就会在特定位置实例化,其旋转方式会与发射器保持一致。然后实例化的障碍会针对玩家的前进方向发射出去,这样障碍就会在玩家正前方着陆。

    现在要保存你的代码。首先,要将ObstacleLauncher脚本粘附到Launcher GameObject。将Obstacle预制件分配到脚本中的抛射变量(你可以将预制件从Assets列表拖拽到变量)。将Player GameObject分配到脚本中的目标变量。

    obstacle_launcher_vars_assigned(from raywenderlich)

    obstacle_launcher_vars_assigned(from raywenderlich)

    在Unity Editor中试玩游戏,确认这些阻块确实是面向Heroic Cube进行发射。调整发射器方向以便阻块在玩家和终点线之间发射。你还可以在Z轴方向通过移动Finish Line GameObject来调整终点线。

    提示:你可以将转变位置设为0,0,2。当你移动Finish Line对象时,子对象也会随之而来,这就是将相关GameObject分组的好处之一。

    gameview_test_launcher(from raywenderlich)

    gameview_test_launcher(from raywenderlich)

    现在多数游戏功能都到位了。下一代你得用一个可显示游戏计时器的任务控制脚本,将所有东西组合到一起,协调玩法并重置场景以便开始新游戏。

    倒计时

    创建一个新脚本资产并将其命名为GameController。打开脚本,删除多余函数并添加以下代码:

    static var gameRunning : boolean = true;

    var gameTimeAllowed : float = 20.0;

    private var gameMessageLabel = “”;
    private var gameMessageDisplay : Rect;
    private var timedOut : boolean = false;
    private var gameTimeRemaining : float = gameTimeAllowed;

    function Awake() {
    gameMessageDisplay = Rect(10, 10, Screen.width – 20, 40);
    }

    function OnGUI() {
    GUI.color = Color.yellow;
    GUI.backgroundColor = Color.black;

    var text : String = “”;
    if (timedOut) {
    gameMessageLabel = “Time’s up!!”;
    } else {
    text = String.Format( “{0:00}:{1:00}”, parseInt( gameTimeRemaining / 60.0 ), parseInt( gameTimeRemaining % 60.0 ) );
    gameMessageLabel = “Time left: ” + text;
    }
    GUI.Box(gameMessageDisplay, gameMessageLabel);
    }

    function Update() {
    if (!gameRunning)
    return;

    // Keep track of time and display a countdown
    gameTimeRemaining -= Time.deltaTime;
    if (gameTimeRemaining <= 0) {
    timedOut = true;
    gameRunning = false;
    }
    }

    Unity提供了易于添加文本标签和按钮功能的GUI控制方式。gameMessageDisplay变量控制的是呈现位置。在此你要让倒计时器呈现的屏幕顶端。

    鼠标点击等事件发生时,系统就会调用OnGUI()。GUI.Box()会使用你之前设置的维度以及当前的游戏信息(游戏邦注:包括倒计时信息、成功消息以及失败消息)创造一个Box Control。

    gameTimeAllowed变量代表游戏计时器,并设为20秒。gameTimeRemaining会追踪当前剩余时间。它原先被设为gameTimeAllowed值,并在Update()中通过Time.deltaTime递减。

    Time.deltaTime是最后一帧完成时的秒数。记住由于帧率可能有所不同,所以这个数值也会有所差别。当gameTimeRemaining值低于0时,timedOut标记设置为真,玩家就会看到超时信息。

    代码还可以设置一个gameRunning标记以便追踪游戏是否在正在运行。这可用于停止倒计时逻辑,你还将在游戏停止运行后用它来控制对象行为。

    将脚本粘附到你的Main Camera对象:

    gamecontroller_added(from raywenderlich)

    gamecontroller_added(from raywenderlich)

    试玩游戏并检测倒计时器呈现方式,以及失败情况。这很难看出问题,但别担心,你可以停止游戏进行调整。

    gameview_test_timeout(from raywenderlich)

    gameview_test_timeout(from raywenderlich)

    时有赢输

    当Heroic Cube穿过终点线或者无形的墙面时,你得呈现成功信息——要鼓励玩家完成挑战的行为!这条信息得包括玩家完成挑战的时间。

    你还得让玩家知晓比赛何时结束。

    最好在GameController脚本中设置这些信息。但是,终点线检测代码在另一个脚本:FinishLineDetection。

    有一个方法可实现这一操作,那就是在GameController中定义一个函数,这样当玩家过线时就会调用FinishLineDetection。之后这个函数就可以通过OnGUI()函数触发所需呈现的信息。

    向GameController脚本添加两个私有变量。一个用于追踪完成挑战的时间,另一个用于标记成功任务(可以将以下代码置于其他现有的私有变量之下):

    private var missionCompleted : boolean = false;
    private var missionCompleteTime : float = gameTimeAllowed;

    然后在GameController脚本末尾添加以下代码:

    function MissionComplete() {
    if (!gameRunning)
    return;

    missionCompleted = true;
    gameRunning = false;

    missionCompleteTime =  gameTimeAllowed – gameTimeRemaining;
    }

    MissionComplete()会检测游戏是否仍在运行。如果是,它就会将私有missionCompleted标记设置为真,将gameRunning标记设置为假。这样可以节省完成任务的时间。

    现在要修改OnGUI()添加成功事例(如下所示)以显示完成挑战的时间信息。新代码添加于 var text : String = “”之下,排列并修改原有的if条件:

    if (missionCompleted) {
    text = String.Format( “{0:00}:{1:00}”, parseInt( missionCompleteTime / 60.0 ), parseInt( missionCompleteTime % 60.0 ) );
    gameMessageLabel = “Mission completed in: ” + text;
    } else if (timedOut) {
    gameMessageLabel = “Time’s up!!”;

    切换到FinishLineDetection脚本并作如下修改:

    #pragma strict

    var gameControllerScript : GameController; // 1: new

    function OnTriggerEnter(other : Collider) {

    if (other.gameObject.tag == “Player”)
    {
    Debug.Log(“You made it!!!”);
    gameControllerScript.MissionComplete(); // 2: new
    }
    }
    @script RequireComponent(Collider)

    新代码数量有限,执行以下操作:

    1.定义一个指向GameController脚本的公共变量。你很快就会如此分配。

    2.在GameController脚本中调用MissionComplete()以触发成功事例。

    after_gamecontroller_var_added(from raywenderlich)

    after_gamecontroller_var_added(from raywenderlich)

    为分配gameControllerScript变量,选择Goal GameObject,然后选择InspectorFinish Line Detection并点击靠近Game Controller脚本的圆形图标。在弹出对话框中,选择Main Camera GameObject并关闭对话。

    assign_gamecontroller_script(from raywenderlich)

    assign_gamecontroller_script(from raywenderlich)

    gamecontroller_var_assigned(from raywenderlich)

    gamecontroller_var_assigned(from raywenderlich)

    试玩游戏并冲向终点线,躲避那些邪恶的阻块。你及时到达终点时要注意查看下信息呈现方式是否正确。

    gameview_test_success(from raywenderlich)

    gameview_test_success(from raywenderlich)

    停止游戏并点击Play再玩一次。检测失败事例的情况,看该逻辑是否仍然可行。

    改变呈现字体

    你可能注意到了,尤其是在使用Unity Remote或iOS设备测试游戏时你会发现,呈现游戏信息的字体非常之小。

    你可以通过http://dafont.com等诸多网站中获得大量字体。你将使用的样本是由免费的Transformers Font创造的。没错,这就是来自《变形金刚》电影中的原字体。

    我将以上字体添加到Resources.zip文件中。

    下载资源档案并提取其中内容。找到字体大小并将Transformers Movie.ttf拖拽到你的Project ViewAssets文件夹中进行输出。在Project View中选择Transformers Movie资产。Inspector显示了输出设置。将字体大小设置为36,点击Apply。

    font_import_settings_changed(from raywenderlich)

    font_import_settings_changed(from raywenderlich)

    你已经输出了你的定制字体,现在可以将其运用到你的项目中。

    打开GameController脚本修改信息字体。定义公共变量以设置字体:

    var gameMessageFont : Font;

    通过修改OnGUI()来改变用于呈现标签的字体,如下所示:

    function OnGUI() {
    GUI.skin.font = gameMessageFont;
    GUI.color = Color.yellow;

    将gameMessageFont的公共变量分配到GUI.skin.font以改变字体。

    font_assigned(from raywenderlich)

    font_assigned(from raywenderlich)

    现在选择Main Camera GameObject。将字体资产拖拽到将gameMessageFont变量中,以便将gameMessageFont分配到新输出的字体。

    gameview_font_test(from raywenderlich)

    gameview_font_test(from raywenderlich)

    预览游戏,并确认呈现信息使用的是新字体。

    现在,这些字体看起来更理想了。

    游戏时间

    下一步,创建一个游戏内置按钮,玩家摁下即可开始,或在失败/成功之后重新开始游戏。只要游戏停止运行,就要呈现这个按钮。重新调用你定义的一个gameRunning标记,使用它来控制按钮的能见度。

    为了创建游戏按钮及相关功能,你必须修改GameController脚本。首先要定义一个控制游戏按钮文本的私有变量:

    private var playButtonText = “Play”;

    然后添加一个称为startGame()的新变量:

    function startGame() {
    // Reset if starting a new game
    gameTimeRemaining = gameTimeAllowed;
    timedOut = false;
    missionCompleted = false;

    // Change button text after the initial run
    playButtonText = “Play Again”;

    // Kick off the game
    gameRunning = true;
    }

    现在修改OnGUI()以便在游戏不运行时呈现按钮,在OnGUI()末尾添加以下代码:

    // The menu button
    if (!gameRunning) {
    var xPos = Screen.width / 2 – 100;
    var yPos = Screen.height / 2 + 100;
    if( GUI.Button( new Rect( xPos, yPos, 200, 50 ), playButtonText ) ) {
    startGame();
    }
    }

    最后,将gameRunning标记设为假。只要修改变量原来的变量将其最初值从“真”改为“假”。

    static var gameRunning : boolean = false;

    OnGUI()会使用GUI.Button()函数放置按钮。按钮文本是一个变量,所以它最初会出现“Play”并在随后每次都出现“Play Again”。

    GUI.Button()包含在if语句中。如果点击按钮就会返回“真”值。当用户点击按钮时,游戏就开始了。startGame()会首先初始化游戏,更改游戏按钮文本并最终将gameRunning标记设为“真”。

    在Unity Editor中预览游戏。确认游戏按钮最初是可视状态,在你点击之后才变成隐藏状态。确认当游戏运行结束时,这个Play按钮会再次恢复可视,其中的文本 也从“Play”变成“Play Again”。注意你在首次之后每回点击这个按钮,时间都会重置 ,倒计时也会重新开始。

    同时也要注意,玩家可以在点击Play按钮之前移动。这有点令人心烦,不要让你的Heroic Cube抢跑。

    为了处理这个细节,要使用gameRunning变量(因为有静态调节器,所以这是一个全局变量)。在MoveAround脚本顶端的Update()添加以下代码:

    if (GameController != null && !GameController.gameRunning)
    return;

    你也可以在游戏不运行时放弃障碍物使发射器失效。在ObstacleLauncher脚本的Update()顶端添加以下代码:

    if (GameController != null && !GameController.gameRunning)
    return;

    预览游戏确认当游戏停止运行时,玩家就无法移动,系统也不会再发射障碍物。

    重新开始游戏

    现在Play按钮已经可以正常运行,你可以会注意到尽管游戏重新开始时计时器已经重置,但游戏其他内容并未重置。

    障碍物停止掉落,它们可能已经达到最大数量,但此时并没有消失。Heroic Cube也并没有倒退回原点。真正的重置会要求游戏刷新,比如障碍清除并重新装载,而玩家也回到了原点。

    实现这一操作的理想方式就是向所有相关团体发送一条信息让它们重置。Heroic Cube就会返回原点,发射器也会重新装载。

    一个方法就是在脚本中定义一个重置函数以处理重置行为。然后用GameObject.SendMessage()调用这个重置函数,以便让依附于GameObject粘附的的一个组件处理这个函数调用。

    以下就是你的执行操作:

    1.你将在MoveAround脚本中定义一个resetGame()函数,当游戏开始时将玩家位置调回原点。

    2.你将在ObstacleLauncher脚本中定义一个resetGame()函数,以便让障碍数量重置为0。

    3.你将依次通过一个给定的GameObjects列表(包括玩家和发射器GameObject),调用GameObject.SendMessage(“resetGame”, …)来激活重置。

    4.当用户重置游戏时,你将在GameController中添加重置逻辑。

    但代码比文字更有说服力。首先,要打开MoveAround脚本并添加以下变量和函数:

    var originalPosition : Vector3;
    var originalRotation : Quaternion;

    function Awake() {
    originalPosition = transform.position;
    originalRotation = transform.rotation;
    }

    function resetGame() {
    // Reset to original position
    transform.position = originalPosition;
    transform.rotation = originalRotation;
    }

    然后开打ObstacleLauncher脚本并添加新函数:

    function resetGame() {
    // Reset to original data
    numObstaclesLaunched = 0;
    }

    下一步打开GameController脚本并添加以下变量:

    var gameObjectsToReset : GameObject [];

    上一行代码定义了一列GameObjects,将由重置游戏函数调用它们激活重置行为。

    现在用以下代码替换原有的startGame()函数:

    function startGame() {
    // Reset if starting a new game
    gameTimeRemaining = gameTimeAllowed;
    timedOut = false;
    missionCompleted = false;

    // Change button text after the initial run
    playButtonText = “Play Again”;

    // Clean out any enemy objects
    var enemies = GameObject.FindGameObjectsWithTag(“Enemy”);
    for (var enemy : GameObject in enemies) {
    Destroy ( enemy);
    }
    // Call all game reset methods
    for (var gameObjectReceiver : GameObject in gameObjectsToReset) {
    gameObjectReceiver.SendMessage(“resetGame”, null, SendMessageOptions.DontRequireReceiver);
    }

    // Kick off the game
    gameRunning = true;
    }

    通过查找带有Enemy标签的GameObjects清除所有敌人实例。在敌人GameObjects中调用Destroy(),在场景中清除所有的障碍物。

    然后由代码处理gameObjectsToReset阵列并向每个GameObject发送一条信息调用resetGame()。这并非一个强制要求阵列中的一个组件执行resetGame()。你需要向处理流程分配对象。

    现在选择Main Camera对象,并为Game Objects To Reset标注新公共变量:

    gameresetarray_added(from raywenderlich)

    gameresetarray_added(from raywenderlich)

    将大小设为2。这个阵元就会扩展。将Player GameObject分配到Element 0,将Launcher GameObject分配到Element 1。

    gamereset_assigned(from raywenderlich)

    gamereset_assigned(from raywenderlich)

    预览游戏并确认游戏在一次成功或失败后已经完全重置。确认玩家已经重置到原点,屏幕上的障碍已经清除,当游戏重新开始时,障碍又开始降落。

    补充完整信息

    你的基本玩法功能现在已经到位了,但最好在游戏刚启动时添加一些额外元素,呈现与游戏相关的信息。现在你呈现的只是一个Play按钮,用户并不知道自己将面临什么情况。

    添加一些欢迎文本,以及一个关于游戏操作规则的简短解释,这样会让游戏显得更为人性化。这个欢迎文本将使用你所输出的Transformers字体。而描述文本则使用Unity绑定的Arial字体。

    创建一个空白的游戏对象,命名为Intro并将转变位置设为0,0,0。

    选择GameObjectCreate Other3D Text创建一个3D Text GameObject并将其命名为Description Text。将InspectorText MeshText属性设为“在时间结束前到达终点线”。将原转变位置设为-10,1,12。

    desc_text_transform_text(from raywenderlich)

    desc_text_transform_text(from raywenderlich)

    在Unity Editor中预览游戏,调整对象的位置令其居于水平中间位置,以便将它看得更清楚。

    提示:你可能很需要调整X和Z轴方向,X位于对象中间,Z用于缩放。

    desc_text_adjusted(from raywenderlich)

    desc_text_adjusted(from raywenderlich)

    用Unity Remote查看游戏,确保文本在iOS设备上查看时具有可视性。必要时可进行调整。

    将Description Text对象放置于Intro GameObject之下。这样做可以让你在之后通过代码方便地显示或隐藏菜单显示信息。

    desc_text_parented(from raywenderlich)

    desc_text_parented(from raywenderlich)

    创建第二个3D Text GameObject,将其命名为Welcome Text。这个文字应该显示于描述文本上方,所以将原转变位置设为-6,5,10。将InspectorText MeshText属性设为“Welcome”。

    从Project View中将字体资产拖拽到Inspector中的Font属性,将Font属性设为Transformers Movie(游戏邦注:也可以点击靠近Font的带点图标圆形,并从弹出列表中选中它):

    welcome_text_transform_text_font(from raywenderlich)

    welcome_text_transform_text_font(from raywenderlich)

    调整Welcome Text位置,这样你在Unity Editor和通过Unity Remote中测试游戏时可以清楚看到它。

    welcome_text_adjusted(from raywenderlich)

    welcome_text_adjusted(from raywenderlich)

    将Welcome Text对象置至Intro GameObject之下。

    当游戏运行时隐藏Intro GameObject(及其子对象)。打开GameController脚本并进行以下调整:

    var intro : Transform;

    function startGame() {

    // Turn off the intro text
    for (var child : Transform in intro ) {
    child.gameObject.renderer.enabled = false;
    }

    // Clean out any enemy objects

    现在你添加了一个新公共变量以处理Intro GameObject。然后你修改startGame()以便让Intro对象不可视(关闭其子游戏对象的渲染器)。

    现在选择Main Camera设置Intro变量,并从Hierarchy View中将Intro GameObject拖拽到InspectorGame ControllerIntro变量进行分配。或者使用圆形点图标来操作,因为这样更简单。

    intro_var_assigned(from raywenderlich)

    intro_var_assigned(from raywenderlich)

    预览游戏以检测当点击Play按钮,游戏开始时,文本是否处于隐藏状态。

    添加音频

    音频在游戏体验中发挥着重要作用,不但可提供感觉反馈,还可以创造氛围。所以你得添加音频以进一步丰富玩法。

    当玩家及时过线,任务失败,或者当障碍物砸到地面或撞向任何事物时,游戏都将触发音效。当然,游戏必须含有一些背景音乐。

    用Unity添加音频包括将一个Audio Source组件粘附到一个GameObject。Audio Source组件有一个Audio Clip属性,你可以将此分配到你想播放的声音中。这个组件还有一些额外属性可控制音乐文件何时播放,以及是否循环播放。其中支持的音频文件格式包 括.AIF, .WAV, .MP3, and .OGG。

    以下两个网站提供了本教程所使用的无版税音频资源:

    * http://incompetech.com/

    *http://freesound.org/ (需注册方可下载音乐)

    你之前下载的Resources.zip文件包含了你将使用的所有音频文件。你也可以采用自己创造的音效而不是我所提供的资源。

    为了便于参考,以下列出这个Resources.zip文件所包含的音频文件原链接:

    *胜利:20784__wanna73__crowd-cheering-football-01.wav

    *失败:83916__timbre__benboncan-sad-trombone-tweaked.wav

    *效果:30668__hardpcm__hardbassdrum002.wav

    *背景: Gustav Sting.mp3

    要注意为了清楚和简便,Resources.zip中的文件已经重新命名。前往你原先从Resources.zip提炼出的文件夹,输出音频文件(将它们拖拽到Project View/Assets文件夹)。

    audio_import_victory(from raywenderlich)

    audio_import_victory(from raywenderlich)

    当音频文件输出到Unity时,你可以说明它是否将被压缩或保持原样(游戏邦注:MP3和Ogg Vorbis音频通常是以压缩格式输出)。

    这有什么关系呢?压缩文件会更小,但它们需要在游戏运行时解压,这会占据CPU周期。你一般会压缩背景音频,至于较短的音效,保持原来的格式会更好,更能提供良好的音质。

    如果音频模式被压缩了,你就可以选择是否使用硬件处理解压,如果是在iOS设备上运行就可以使用苹果硬件解码器。硬件速度更快,但硬件一次只能解压一个音乐文件。

    你还可以将声音设为3D格式。这意味着当声音播放时,音效就会与GameObject的3D位置相对应。例如,GameObject若在远方,其声音也就会更轻。

    audio_import_background(from raywenderlich)

    audio_import_background(from raywenderlich)

    在Project View中选择背景音频以显示输出设置。不选中3D Sound选项,选择硬件解码。点击Apply保存这一设置调整。

    其他音频文件是.WAV格式,你不需要修改默认的输出设置,将其设为3D和原音频格式。

    为了听到声音,你的屏幕需要一个添加到GameObject的Audio Listener组件。场景中只能有一个Audio Listener,这个监听器将从离自己最接近的音频资源挑选声音,并将其发送到设备扬声器。

    在默认状态下,Audio Listener依附于Main Camera。你也可以将它粘附到一个不同的GameObject,例如玩家。

    在这款游戏中,你将让Audio Listener保留在Main Camera中,但你创建自己的游戏时可以尝试不同的选项。

    成功与失败的声音

    你将把音频添附到Goal GameObject以模拟终点线上的群体欢呼声或嘲弄的声音。

    在Hierarchy View选择Goal GameObject,并通过选择ComponentAudioAudio Source添加一个音频源。将胜利的音频资产设置到InspectorAudio SourceAudio Clip属性。除去Play on Awake选项。

    goal_audio_added(from raywenderlich)

    goal_audio_added(from raywenderlich)

    现在创建一个新的JavaScript资产以便用于播放成功或失败的声音。将新脚本命名为FanReaction。打开新脚本去除无关函数,并添加以下代码:

    var audioVictory : AudioClip;
    var audioDefeat : AudioClip;
    var volumeVictory : float = 2.0;
    var volumeDefeat : float = 2.0;

    function playSoundOfVictory(isVictory : boolean) {
    // Stop any current audio
    if (audio.isPlaying)
    audio.Stop();

    // Play either the sound of victory or defeat.
    audio.clip = isVictory ? audioVictory : audioDefeat;
    audio.volume = isVictory ? volumeVictory : volumeDefeat;
    audio.Play();
    }

    function resetGame() {
    // Reset to original state, stop any audio
    if (audio.isPlaying)
    audio.Stop();
    }

    @script RequireComponent(AudioSource)

    这个脚本有两个音频片段,一个是针对胜利,一个针对失败。playSoundOfVictory()函数会先停止目前正在播放的任何音频,然后根据isVictory输入播放所需音频。

    resetGame()会中止当前正在播放的任何音频。你将快速接通GameController在游戏每次重启时调用resetGame()。

    将这个新脚本附加到Goal GameObject上。将Victory音频资产设为Audio Victory变量。将Defeat音频资产设为Audio Defeat变量。

    fan_script_added(from raywenderlich)

    fan_script_added(from raywenderlich)

    编译GameController脚本并作如下调整:

    var fanReactionScript : FanReaction;

    function Update() {
    if (!gameRunning)
    return;

    // Keep track of time and display a countdown
    gameTimeRemaining -= Time.deltaTime;
    if (gameTimeRemaining <= 0) {
    timedOut = true;
    gameRunning = false;

    // Play the sound of defeat
    fanReactionScript.playSoundOfVictory(false);
    }
    }

    function MissionComplete() {
    if (!gameRunning)
    return;

    missionCompleted = true;
    gameRunning = false;

    // Play the sound of victory
    fanReactionScript.playSoundOfVictory(true);

    missionCompleteTime =  gameTimeAllowed – gameTimeRemaining;
    }

    这段代码定义了一个引用FanReaction脚本的新公共变量。你修改MissionComplete()以调用 playSoundOfVictory(),设置为真以播放胜利的声音。你还要修改Update()以调用playSoundOfVictory(),设 置为假来播放失败声音。

    你现在可以将Goal GameObject中的FanReaction脚本链接到Main Camera‘s GameController脚本组件的变量。选择Main Camera然后在Inspector中的GameController之下,靠近fanReactionScript变量的圆形图标。在弹出对话中,选 择Goal GameObject,然后关闭弹出对话。

    fan_var_assigned(from raywenderlich)

    fan_var_assigned(from raywenderlich)

    在FanReaction中调用resetGame(),选择Main Camera Object。在Inspector中的Game Controller组件片段中,将Game Objects To Reset中的数组大小从2增加到3。将Goal GameObject设为Element 2。

    game_reset_array_assigned(from raywenderlich)

    game_reset_array_assigned(from raywenderlich)

    预览游戏并检测胜利和失败场景以确保游戏正确播放声音。确认你点击Play Again时这些声音就会停止。

    3D音效

    如果障碍物落地时也能添加一些声音,效果会更好。为达到这一目标,你将把一个音频源添附到Obstacle预制件,然后选择碰撞,这样当障碍物降落或撞击到任何物体时都能播放音效。

    将Audio Source组件添加到Obstacle预制件。将impact音频分配到Audio Clip属性。

    audio_impact_added(from raywenderlich)

    audio_impact_added(from raywenderlich)

    创建一个新JavaScript资产并将其重命名为ObjectCollision。编译脚本,删除无关的函数并添加以下代码:

    var impact : AudioClip;
    function OnCollisionEnter () {
    audio.PlayOneShot(impact);
    }

    @script RequireComponent(AudioSource)

    这些代码执行的是预定义的OnCollisionEnter()事件函数,并调用audio.PlayOneShot()函数来播放impact音频片段。audio.PlayOneShot()说明了另一种播放音频的方法,允许你在想播放时传送音频。

    将脚本添附到Obstacle预制件。将impact音频资产设为脚本中的Impact变量。

    collision_script_added(from raywenderoich)

    collision_script_added(from raywenderoich)

    预览游戏并确认当障碍物着地或撞到某物时,游戏会发出令人满意的声音。注意障碍物超贴近玩家,声音就会越大。

    添加背景音乐

    现在你的游戏基本成型了。但还有一物没到位——一些背景音乐。

    音乐在塑造游戏氛围上发挥了重要作用。它可以让用户的肾上腺素产生反应,并通过提供一些诸如鸟鸣或狼嗥的环境音,让他们“感受”游戏环境。所以要添加一些背景音乐。

    将一个Audio Source组件添加到Main Camera GameObject。将Background音频资产设为Audio Clip属性。

    选择Play on Awake以及Loop选项。这可以确保游戏一开始背景音乐也跟着开始,并且会持续播放。

    将音量属性调整为0.1,这样它就不会淹没其他声音。如果你使用的是自己创造的声音,那就根据自己的音乐默认音量大小来调整,以达到同样的效果。要让用户在可听到所有音效的同时,听到背景音乐。

    background_audio_added(from raywenderlich)

    background_audio_added(from raywenderlich)

    在Unity Editor中预览项目。在你完全满意时,将项目部署到iOS设备,这时你将在Build Setting中添加Level_3场景。

    build_settings_level3(from raywenderlich)

    build_settings_level3(from raywenderlich)

    在你的iOS设备中测试玩法,试听你刚添加的背景音。

    gameview_ios_complete(from raywenderlich)

    gameview_ios_complete(from raywenderlich)

    总结

    恭喜你,现在你已经通关掌握了Unity的基本用法。但对于Unity游戏,你还只是接触了一点表层的内容,你还有更多需要学习和掌握的东西。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦)

     
     
     
     
     
  • 相关阅读:
    python query-string处理Query String Parameters参数
    谷歌chrome浏览器大量书签消失,怎么恢复历史?
    VUE懒加载的table前端搜索
    SQL排序分组
    使用sqlparse解析table_name,超级强大,支持子查询, left join等
    20211008杂记
    [BZOJ2216|Luogu P3515] [Poi2011]Lightning Conductor (线性解法)
    矩阵的特征值和特征向量
    dp多维状态的优化
    02-servlet基本介绍访问流程生命周期Service、doGet、doPost
  • 原文地址:https://www.cnblogs.com/banchuangshuying/p/5431644.html
Copyright © 2011-2022 走看看