在第三部分中,你将给项目添加游戏玩法。这里不是简单地在场景中四处移动,而是让你的Heroic Cube在特定时间内撞击到达终点线。 为了给玩家制造一些挑战,我们要在Cube奔向终点的过程中设置一些障碍。为此我们要添加一个定时器,并在获胜时添加欢呼声,失败时则是死寂般的沉默。 开始 首先,将Level_2场景保存成新场景并命名为Level_3。在第三部分教程中,你将针对新场景改变原有内容。 你可以用一个粗线将两个标杆连接起来创造终点线,这样玩家可以清楚地看到目的地。终点线将包括一个无形的墙,如果Heroic Cube过线了就会触发相应的结果。 选择GameObject\Create Empty创造一个代表终点线的新对象。这是一个GameObject的母对象,其子对象将包括标杆、线条和墙体。 将这一对象通过Inspector仪表盘或者右击对象,选择“重命名”将其重命名为Finish Line。将转变位置设为0,0,0。 要创造第一个标杆,选择GameObject\Create Other\Cylinder,将其重命名为Post1。将转变范围设为1,3,1。将转变位置设为-10,0,20令其呈现在玩家左前方。使用Move Tool,调整Y位置,让圆柱体底部就略低于地面。 提示:从Z轴查看场景有助于进行调整。 post1_after_height_adjusted(from raywenderlich)
拖拽Post1 GameObject并将其放置于Finish Line GameObject,让后者成为Post1的母体。 post1_parented(from raywenderlich)
要创造第二个标杆,选择Post1,右击并选择“复制”。再右击一次并选择“粘贴”。将这个新的GameObject从Post1重命名为Post2。使用Move Tool,调整X位置以便标杆立于玩家右侧。 提示:从Y轴查看场景以便做出调整。或者,将转变X位置设为10也可以。 post2_after_xposition_adjusted(from raywenderlich)
下一步,创造用于检测Cube是否穿过终点线的墙体。选择GameObject\Create Other\Cube,将其重命名为Goal。将转变范围设为24,10,0.5。将原转变位置设为0,2,0。 goal_scale_adjusted(from raywenderlich)
将墙体移动到两个标杆之后。如果必要的话,调整X范围值,以便墙体从一个标杆延伸到另一个标杆。 goal_scale_position_adjusted(from raywenderlich)
现在墙体仍在选择状态,打开Inspector\Box Collider组件,查看Is Trigger数值。取消选中Mesh Renderer组件以便让墙体隐形。 boxcollider_istrigger_meshoff(from raywenderlich)
拖拽Goal GameObject,使其位于Finish Line GameObject之下,成为后者的子对象。 goal_parented(from raywenderlich)
连接节点 下一步就是创造连接标杆的线条,以便玩家清楚看到终点线。你将通过脚本从一个标杆到另一个标杆绘制出线段。 选择Asset\Create\JavaScript创造新脚本并将其命名为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组件(Components\Effects\Line 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)
分别将Post1和Post2 GameObject分配到Post 1和Post 2变量。 finishline_vars_assigned(from raywenderlich)
在Unity Edito中预览游戏。你会看到两个标杆以及一条绿线所组成的终点线。 gameview_finishline 1(from raywenderlich)
下一步代将创造一个能够检测超过终点线这一行为的新脚本,并将这一新脚本粘附到Goal GameObject中。选择Assets\Create\JavaScript并将脚本命名为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脚本拖拽到Inspector\Add Component按钮,以将该脚本组件粘附到GameObject。 在你拖拽玩家之前,为玩家GameObject命名(而不是简单的旧名“cube”)。为了保持一致,将Cube GameObject重命名为Player。 cube_renameto_player(from raywenderlich)
现在,为玩家对象添加一个标签,以便终点线检测逻辑生效。在Inspector中下拉标签选项并选中Player。 player_tag_select(from raywenderlich)
player_tagged(from raywenderlich)
玩家是预建标签之一。之后,你将创建自己的标签以便鉴定游戏中所有的敌人。 点击Play并将Heroic Cube移到终点线之后。你就会看到“你成功了!!!”这一记录信息,你就知道玩家此时已经穿过终点线。 gameview_finishline(from raywenderlich)
没错,Heroic Cube能够到达目标并赢得游戏,但你不能让它太轻松实现。你还得为游戏添加两层复杂性:一个基于时间的挑战和多个障碍。首先,让我们添加障碍。 创建发射器 创建一个空白的GameObject,并将其命名为Launcher,代表将阻止Heroic Cube前进步伐的障碍发射中心。 在Z轴方向上,使用Move Tool将发射器对象放置于玩家和终点线之间,并且位于玩家之上。你可以将转变位置设为0,12,8,并根据需要进行调整。 launcher_adjusted(from raywenderlich)
发射器的主要存在理由就是发射障碍,所以你得给它提供一些发射物。 在Unity中创造的弹药一般是先设计一些GameObject,然后创造一些能够在游戏玩法场景中实例化的预制件。你将创建一个Obstacle GameObject,将其转变为预制件,然后让发射器将其射向倒霉的玩家。 创建障碍 创建一个方形的GameObject,并将其命名为Obstacle。把转变范围设为2,2,2,让它比玩家更大并更具威慑感。这就是那些将扮演坏蛋的立方体。 让这些障碍看起来更有趣一些,而不只是默认的灰色物质。为匹配完整的样本,首先要从角色控制器包中输出一个材料:选择Assets\Import Package\Character Controller,然后选中constructor_done材料以及在下图中显示的相关纹理,最后点击Import。 obstacle_material_import(from raywenderlich)
新材料会显示在你的Project View中。 obstacle_material_after_import(from raywenderlich)
选择Obstacle GameObject。通过调整Inspector\Mesh Renderer\Materials\Element 0属性改变渲染材料。点击靠近属性的圆形图标,产生“材料选择”对话框。 obstacle_select_material(from raywenderlich)
选择你刚输出的constructor_done材料,关闭“材料选择”对话框。 obstacle_after_material_added(from raywenderlich)
现在你必须为Obstacle GameObject贴标签,以便开始新游戏时清除场景中的Obstacle实例。 为此,创建一个名为Enemy的新标签。点击Inspector\Tag\Add Tag。TagManager将显示在右侧栏。点击靠近标签的三角形来扩展标签阵列。将Element 3的值设为Enemy。 add_enemy_tag(from raywenderlich)
选择Obstacle GameObject,并为这一对象贴上新的Enemy标签。 obstacle_tagged(from raywenderlich)
当Obstacle被具体化时,你将添加的代码则需要将一个刚体组件粘附到障碍上。这要先添加一个刚体。选择Component\Physics\Rigidbody(游戏邦注:Obstacle仍在选中状态): rigidbody_added(from raywenderlich)
点击Project View中的Assets文件夹。选择Assets\Create\Prefab,为你的障碍物创建一个预制件。此时Project View会显示一个空白的预制件。将其命名为障碍。 create_prefab(from raywenderlich)
注:如果你的资产图标大于以上截图所示情况,并且不知该如何查看资产列表中的物品,只需向左下滑资产列表就能看到。 将Obstacle GameObject拖拽到这个新预制件中。 obstacle_prefab(from raywenderlich)
预制件变成蓝色表明它已经被指定。 现在你已经创建了一个可作为重用资产的预制件,在这个场景中已经不再需要这个预制件。发射器将负责在必要的时候具体化一个Obstacle实例。在Hierarchy View中,选择Obstacle GameObject,右击并选择“删除”。 释放障碍 下一步,通过脚本创造逻辑以发射障碍,完成这一过程。 创造一个新脚本资产并将其命名为ObstacleLauncher。 提示:你可以在Project View中右击并选择Create\JavaScript以创建一个脚本。 打开新脚本,用以下代码替换原来的函数: 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)
在Unity Editor中试玩游戏,确认这些阻块确实是面向Heroic Cube进行发射。调整发射器方向以便阻块在玩家和终点线之间发射。你还可以在Z轴方向通过移动Finish Line GameObject来调整终点线。 提示:你可以将转变位置设为0,0,2。当你移动Finish Line对象时,子对象也会随之而来,这就是将相关GameObject分组的好处之一。 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)
试玩游戏并检测倒计时器呈现方式,以及失败情况。这很难看出问题,但别担心,你可以停止游戏进行调整。 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)
为分配gameControllerScript变量,选择Goal GameObject,然后选择Inspector\Finish Line Detection并点击靠近Game Controller脚本的圆形图标。在弹出对话框中,选择Main Camera GameObject并关闭对话。 assign_gamecontroller_script(from raywenderlich)
gamecontroller_var_assigned(from raywenderlich)
试玩游戏并冲向终点线,躲避那些邪恶的阻块。你及时到达终点时要注意查看下信息呈现方式是否正确。 gameview_test_success(from raywenderlich)
停止游戏并点击Play再玩一次。检测失败事例的情况,看该逻辑是否仍然可行。 改变呈现字体 你可能注意到了,尤其是在使用Unity Remote或iOS设备测试游戏时你会发现,呈现游戏信息的字体非常之小。 你可以通过http://dafont.com等诸多网站中获得大量字体。你将使用的样本是由免费的Transformers Font创造的。没错,这就是来自《变形金刚》电影中的原字体。 我将以上字体添加到Resources.zip文件中。 下载资源档案并提取其中内容。找到字体大小并将Transformers Movie.ttf拖拽到你的Project View\Assets文件夹中进行输出。在Project View中选择Transformers Movie资产。Inspector显示了输出设置。将字体大小设置为36,点击Apply。 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)
现在选择Main Camera GameObject。将字体资产拖拽到将gameMessageFont变量中,以便将gameMessageFont分配到新输出的字体。 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)
将大小设为2。这个阵元就会扩展。将Player GameObject分配到Element 0,将Launcher GameObject分配到Element 1。 gamereset_assigned(from raywenderlich)
预览游戏并确认游戏在一次成功或失败后已经完全重置。确认玩家已经重置到原点,屏幕上的障碍已经清除,当游戏重新开始时,障碍又开始降落。 补充完整信息 你的基本玩法功能现在已经到位了,但最好在游戏刚启动时添加一些额外元素,呈现与游戏相关的信息。现在你呈现的只是一个Play按钮,用户并不知道自己将面临什么情况。 添加一些欢迎文本,以及一个关于游戏操作规则的简短解释,这样会让游戏显得更为人性化。这个欢迎文本将使用你所输出的Transformers字体。而描述文本则使用Unity绑定的Arial字体。 创建一个空白的游戏对象,命名为Intro并将转变位置设为0,0,0。 选择GameObject\Create Other\3D Text创建一个3D Text GameObject并将其命名为Description Text。将Inspector\Text Mesh\Text属性设为“在时间结束前到达终点线”。将原转变位置设为-10,1,12。 desc_text_transform_text(from raywenderlich)
在Unity Editor中预览游戏,调整对象的位置令其居于水平中间位置,以便将它看得更清楚。 提示:你可能很需要调整X和Z轴方向,X位于对象中间,Z用于缩放。 desc_text_adjusted(from raywenderlich)
用Unity Remote查看游戏,确保文本在iOS设备上查看时具有可视性。必要时可进行调整。 将Description Text对象放置于Intro GameObject之下。这样做可以让你在之后通过代码方便地显示或隐藏菜单显示信息。 desc_text_parented(from raywenderlich)
创建第二个3D Text GameObject,将其命名为Welcome Text。这个文字应该显示于描述文本上方,所以将原转变位置设为-6,5,10。将Inspector\Text Mesh\Text属性设为“Welcome”。 从Project View中将字体资产拖拽到Inspector中的Font属性,将Font属性设为Transformers Movie(游戏邦注:也可以点击靠近Font的带点图标圆形,并从弹出列表中选中它): welcome_text_transform_text_font(from raywenderlich)
调整Welcome Text位置,这样你在Unity Editor和通过Unity Remote中测试游戏时可以清楚看到它。 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拖拽到Inspector\Game Controller\Intro变量进行分配。或者使用圆形点图标来操作,因为这样更简单。 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)
当音频文件输出到Unity时,你可以说明它是否将被压缩或保持原样(游戏邦注:MP3和Ogg Vorbis音频通常是以压缩格式输出)。 这有什么关系呢?压缩文件会更小,但它们需要在游戏运行时解压,这会占据CPU周期。你一般会压缩背景音频,至于较短的音效,保持原来的格式会更好,更能提供良好的音质。 如果音频模式被压缩了,你就可以选择是否使用硬件处理解压,如果是在iOS设备上运行就可以使用苹果硬件解码器。硬件速度更快,但硬件一次只能解压一个音乐文件。 你还可以将声音设为3D格式。这意味着当声音播放时,音效就会与GameObject的3D位置相对应。例如,GameObject若在远方,其声音也就会更轻。 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,并通过选择Component\Audio\Audio Source添加一个音频源。将胜利的音频资产设置到Inspector\Audio Source\Audio Clip属性。除去Play on Awake选项。 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)
编译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)
在FanReaction中调用resetGame(),选择Main Camera Object。在Inspector中的Game Controller组件片段中,将Game Objects To Reset中的数组大小从2增加到3。将Goal GameObject设为Element 2。 game_reset_array_assigned(from raywenderlich)
预览游戏并检测胜利和失败场景以确保游戏正确播放声音。确认你点击Play Again时这些声音就会停止。 3D音效 如果障碍物落地时也能添加一些声音,效果会更好。为达到这一目标,你将把一个音频源添附到Obstacle预制件,然后选择碰撞,这样当障碍物降落或撞击到任何物体时都能播放音效。 将Audio Source组件添加到Obstacle预制件。将impact音频分配到Audio Clip属性。 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)
预览游戏并确认当障碍物着地或撞到某物时,游戏会发出令人满意的声音。注意障碍物超贴近玩家,声音就会越大。 添加背景音乐 现在你的游戏基本成型了。但还有一物没到位——一些背景音乐。 音乐在塑造游戏氛围上发挥了重要作用。它可以让用户的肾上腺素产生反应,并通过提供一些诸如鸟鸣或狼嗥的环境音,让他们“感受”游戏环境。所以要添加一些背景音乐。 将一个Audio Source组件添加到Main Camera GameObject。将Background音频资产设为Audio Clip属性。 选择Play on Awake以及Loop选项。这可以确保游戏一开始背景音乐也跟着开始,并且会持续播放。 将音量属性调整为0.1,这样它就不会淹没其他声音。如果你使用的是自己创造的声音,那就根据自己的音乐默认音量大小来调整,以达到同样的效果。要让用户在可听到所有音效的同时,听到背景音乐。 background_audio_added(from raywenderlich)
在Unity Editor中预览项目。在你完全满意时,将项目部署到iOS设备,这时你将在Build Setting中添加Level_3场景。 build_settings_level3(from raywenderlich)
在你的iOS设备中测试玩法,试听你刚添加的背景音。 gameview_ios_complete(from raywenderlich)
总结 恭喜你,现在你已经通关掌握了Unity的基本用法。但对于Unity游戏,你还只是接触了一点表层的内容,你还有更多需要学习和掌握的东西。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦)
|