游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

使用Unity 3D开发iOS游戏入门教程(3)

发布时间:2013-03-28 17:43:18 Tags:,,,

作者:Christine Abernathy

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

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

在第三部分中,你将给项目添加游戏玩法。这里不是简单地在场景中四处移动,而是让你的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_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是否穿过终点线的墙体。选择GameObject\Create Other\Cube,将其重命名为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)

现在墙体仍在选择状态,打开Inspector\Box 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)

连接节点

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

选择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)

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中。选择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)

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,让它比玩家更大并更具威慑感。这就是那些将扮演坏蛋的立方体。

让这些障碍看起来更有趣一些,而不只是默认的灰色物质。为匹配完整的样本,首先要从角色控制器包中输出一个材料:选择Assets\Import Package\Character 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。通过调整Inspector\Mesh Renderer\Materials\Element 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的新标签。点击Inspector\Tag\Add 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被具体化时,你将添加的代码则需要将一个刚体组件粘附到障碍上。这要先添加一个刚体。选择Component\Physics\Rigidbody(游戏邦注:Obstacle仍在选中状态):

rigidbody_added(from raywenderlich)

rigidbody_added(from raywenderlich)

点击Project View中的Assets文件夹。选择Assets\Create\Prefab,为你的障碍物创建一个预制件。此时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中右击并选择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)

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,然后选择Inspector\Finish 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 View\Assets文件夹中进行输出。在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。

选择GameObject\Create Other\3D Text创建一个3D Text GameObject并将其命名为Description Text。将Inspector\Text Mesh\Text属性设为“在时间结束前到达终点线”。将原转变位置设为-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。将Inspector\Text Mesh\Text属性设为“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拖拽到Inspector\Game Controller\Intro变量进行分配。或者使用圆形点图标来操作,因为这样更简单。

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,并通过选择Component\Audio\Audio Source添加一个音频源。将胜利的音频资产设置到Inspector\Audio Source\Audio 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编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

Beginning Unity 3D for iOS: Part 3/3

Learn how to use Unity to make a simple 3D iOS game!

This is a post by Tutorial Team Member Christine Abernathy, an Engineer on the Developer Advocacy team at Facebook.

Welcome to the third and final part the Beginning Unity 3D for iOS tutorial series!

In the first part of this series, you toured the basic Unity tools, created a game with a simple player control mechanism and learned how to deploy your project on iOS.

Then in the second part of the series, you enhanced the movement of your Heroic Cube and brought some life to the world it occupies with sky, grass, trees and a variable terrain.

In this third and final part, you’ll add gameplay to the project. Instead of simply moving around the scene, your Heroic Cube will have to dash (as cubes do) to a finish line within a certain
amount of time.

To give the player a challenge, obstacles will rain down on the Cube as it zigs and zags its way to that finish line. A countdown timer will add to the drama. Success will be greeted with cheers –
failure with the deafening silence of defeat. :]

You’re almost to the finish line too, so remember – it’s hip to be square!

Getting Started: The End in Sight!

First, save the Level_2 scene as a new scene named Level_3. You’ll be making all your changes for Part 3 of the tutorial in this new scene.

You’ll create a finish line using two posts and a thick line connecting them, so that the player can clearly see the target destination. The finish line will include an invisible wall that’s used as a trigger when the Heroic Cube crosses the finish line.

Select GameObject\Create Empty to create a new object that represents the finish line. This is the parent GameObject under which you’ll add posts, a line, and a wall.

Rename the object to Finish Line via the Inspector panel or by right-clicking the object and selecting Rename. Set the Transform Position to 0,0,0.

To create the first post, select GameObject\Create Other\Cylinder. Rename it to Post1. Set the Transform Scale to 1,3,1. Set the Transform Position to -10,0,20 to put it in front of the player and to the left. Using the Move Tool, adjust the y position so that the bottom of the cylinder object is just a little below the ground.

Hint: View the scene from the z-axis to help you make the adjustments.

Post height adjustment.

Drag the Post1 GameObject and place it under the Finish Line GameObject to set the latter as the parent for Post1.

Post parenting.

To create a second post, select Post1, right-click and select Copy. Right-click once more and select Paste. Rename the new GameObject from Post1 to Post2. Using the Move Tool, adjust the x position so that the post is to the right of the player.

Hint: View the scene from the y-axis (from above) to help you make adjustments. Alternatively, setting the Transform x position to 10 should do the trick.

Second post position adjustment.

Next, create the wall that helps you detect when the finish line is crossed. Select GameObject\Create Other\Cube and rename it to Goal. Set the Transform Scale to 24,10,0.5. Set the initial

Transform Position to 0,2,0.

Goal scale adjustment.

Move the wall to just behind the two posts. If you need to, adjust the x scale value so the wall stretches from one post to the other.

Goal scale and position adjustment.

With the wall still selected, open the Inspector\Box Collider component and check the Is Trigger value. Uncheck the Mesh Renderer component to make the wall invisible.

Goal trigger and renderer modifications.

Drag the Goal GameObject and place it under the Finish Line GameObject to parent the object.

Goal parenting.

Connect the Dots

Next you’ll create the line connecting the posts so that the player can clearly see the finish line. You’ll do this by drawing a line from one post to the other via script.

Select Asset\Create\JavaScript to create the new script and name it FinishLineRender. Open the script by double-clicking it in the Project View. Delete the stubbed out functions and add the
following code:

// 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);
}

The LineRenderer class allows you to draw lines in 3D space. Given an array of points, you can use the Line Renderer component (Components\Effects\Line Renderer) to draw straight lines.

You could have added the Line Renderer component to the Finish Line object and hard-coded the transform positions for Post1 and Post2, but it’s easier to create the Line Renderer through code.

That’s what you’re doing here.

You draw the line in the Start() function, as it only needs to happen once. First you add the LineRenderer script interface component, then you set the first and second points for the line to the values from the variable inputs that will be tied to the two posts. You set the material for the renderer.

Finally, you set the color for the start and end of the line. The line color variable is made public so you can change it.

Attach the FinishLineRender script component to the Finish Line GameObject.

Hint: You can add the script to the GameObject by selecting the Finish Line object and then tapping the Add Component button in the Inspector. This should bring up a search box – simply type the first few letters of the word “FinishLineRender” and it should show you the script.

Finish Line script added.

Assign the Post1 and Post2 GameObjects to the Post 1 and Post 2 variables, respectively.

Finish Line script variables assigned.

Preview the game in the Unity Editor. You should see two goal posts and a green line across them indicating the finish line. Stop the game.

Game view with finish line.

Next you’ll create a new script that detects the finish line crossing event, and will attach this new script to the Goal GameObject. To do this, select Assets\Create\JavaScript and name the script FinishLineDetection.

Open the new script and delete the stubbed out functions. Add the following code:

function OnTriggerEnter(other : Collider) {

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

You call the OnTriggerEnter() function whenever another collider enters the GameObject. The GameObject needs to be set up as a trigger for the event to fire (which you’ve already done for the Goal object).

The player GameObject has a Character Controller component that is a Collider. So when the player runs into the Goal GameObject, the OnTriggerEnter() event is fired.

In your code, you check if the GameObject that entered the Goal has a tag named “Player”. If that’s the case, the Heroic Cube has crossed the finish line.

Attach the FinishLineDetection script to the Goal GameObject.

Hint: With the Goal object selected in the Hierarchy View, you can drag the FinishLineDetection script from the Project View to the Inspector\Add Component button to attach the script component to the GameObject.

Before you tag the player, give the player GameObject a name other than plain old “cube”. To keep things consistent, rename the Cube GameObject to Player.

Player GameObject renamed.

Now, add a tag to the player object to enable the finish line detection logic. In the Inspector for the player, drop down the Tag selection and choose Player.

Player tag selection.

Player tagged.

Player is one of the pre-built tags available. Later on, you’ll create your own tags to help identify all enemies in the game.

Click Play and move the Heroic Cube past the goal line. You should see a “You made it!!!” log message letting you know that the player crossed the finish line.

Finish line detection test.

Yes, the Heroic Cube can get to the goal line and win the game, but you can’t let it off so easy. You’ll add two levels of complexity to the game: a time-based challenge and obstacles. First, let
‘s add the obstacles.

Create the Launcher

Create an empty GameObject and add it to the scene. Name it Launcher. This represents the evil block empire that’s launching obstacles to put a stop to the Heroic Cube’s advances.

Using the Move Tool, place the launcher object in between the player and the finish line in the z direction and above the player. You can start with a Transform Position of 0,12,8 and tweak it as necessary.

Launcher adjustment.

The main reason for the launcher’s existence is to launch obstacles, so you need to give it some to launch!

Ammunition is typically created in Unity by designing GameObjects and then creating Prefabs that can be instantiated in the scene, as required, during gameplay. You’ll create an Obstacle

GameObject, turn it into a Prefab, and then let the Launcher take care of launching it onto the hapless player.

Create the Obstacles

Create a cube GameObject and name it Obstacle. Set the Transform Scale to 2,2,2 so it’s bigger than the player and hence more intimidating. These are cubes that have gone to the Dark Side. :]

Give the obstacle an interesting look other than the default grey matter. To match the completed sample, first import a material from the Character Controller package: select Assets\Import

Package\Character Controller, then select the constructor_done material and the relevant textures as shown in the image below, and finally click Import.

Import package material for obstacle.

The new material should show up in your Project View.

Material for obstacle imported.

Select the Obstacle GameObject. Change the render material by modifying the Inspector\Mesh Renderer\Materials\Element 0 property. Click on the circular icon next to the property to bring up the Select Material dialog.

Selection of obstacle material.

Select the constructor_done material you just imported. Close the Select Material dialog.

Obstacle material assigned.

Now you must tag the Obstacle GameObject so that later on you can take care of clearing out Obstacle instances of the scene when a new game is started.

For this, create a new tag named Enemy. Click on Inspector\Tag\Add Tag. The TagManager will show up in the right side panel. Expand the Tags array by clicking on the triangle next to the Tags label). Set the value of Element 0 to Enemy.

Adding an enemy tag.

Select the Obstacle GameObject and tag the object with the new Enemy tag.

Adding an enemy tag.

When Obstacle is instantiated, the code you’ll add will expect a Rigidbody component to be attached to the obstacle. Set that up by adding a Rigidbody. Select Component\Physics\Rigidbody (with Obstacle still selected):

Rigidbody component added to obstacle.

Click on the Assets folder in the Project View. Create a Prefab of your obstacle by selecting Assets\Create\Prefab. The Project View should show an empty Prefab. Name it Obstacle.

Empty Prefab created.

Note: If you have larger asset icons than in the screenshot above and wonder how you can get the list view of asset items, simply slide the slider below the asset list all the way to the left. :]

Drag the Obstacle GameObject into this new Prefab.

Obstacle assigned to Prefab.

The Prefab changes to a blue color to indicate that it has been assigned.

Now that you’ve created the Prefab as a reusable asset, you no longer need it in the scene. The launcher will take care of instantiating an Obstacle instance when needed. In the Hierarchy View, select the Obstacle GameObject, right-click and select Delete.

Release the Krak… Err, Obstacles

Next, complete the process by creating logic through a script to launch obstacles.

Create a new script asset and name it ObstacleLauncher.

Hint: You can also right-click in the Project View and select Create\JavaScript to create a script.

Open the new script and replace the stub functions with the following code:

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++;
}
}

The launcher is programmed to launch a certain number of obstacles just in front of the player. It therefore needs an input that represents the player. Previously, when assigning GameObjects to script, you’ve done so by dragging the GameObject to the script variable using the Editor. The Start() function code shows another way to do this.

In Start(), a check is made to see if there is no target assigned. If no target is found, the code looks for a GameObject with the Player tag and assigns this GameObject to the target variable.

The GameObject.FindGameObjectWithTag() function call is typically an expensive call, as it needs to look through all GameObjects. So you’ll want to call this in Start() (which gets called once)
and avoid putting it in, say, Update() (which gets called multiple times).

In the code, Update() first checks if the Launcher has sent out the maximum obstacles allowed. If not, it also checks if a set time interval has passed. This is to avoid launching too many obstacles within a short amount of time.

If it’s time to launch another obstacle, then the Obstacle Prefab is instantiated at the position and rotation corresponding to the Launcher. The instantiated obstacle is then launched in a direction that matches the player’s forward direction, so as to land just in front of the player.

Now save your code and tie up loose ends. First, attach the ObstacleLauncher script to the Launcher GameObject. Assign the Obstacle Prefab to the projectile variable in the script (you can drag the Prefab from the Assets list to the variable). Assign the Player GameObject to the target variable in the script.

Launcher variables assigned.

Play the game in the Unity Editor and verify that the blocks are launched in front of the Heroic Cube as it moves around. Adjust the launcher’s position so that the blocks are launched in between the player and the finish line. You can also adjust the finish line by moving the Finish Line GameObject in the z direction, away from the player.

Hint: You can set the Transform Position to 0,0,2. When you move the Finish Line object, the child objects come along for the ride, which is one perk of parenting or grouping related GameObjects.

Game view of launcher test.

You have most of the game functionality working now. Next you’ll pull everything together with a mission control script that displays the game timer, coordinates the gameplay and resets the scene to start a new game.

The Final Countdown

Create a new script asset and name it GameController. Open the script, delete the stub functions and add the following code:

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 provides GUI controls that make it easy to add text labels and button functionality. The gameMessageDisplay variable controls where the display is shown. Here you set up the countdown timer to display across the top of the screen.

OnGUI() is called when an event occurs such as a mouse click, or at least once a frame. GUI.Box() creates a Box Control using the dimensions you set up initially and with the current game message, which consists of the countdown time info, a success message or a failure message.

The gameTimeAllowed variable represents the game timer and is set to 20 seconds. The gameTimeRemaining variable tracks the currently remaining time. It is initially set to the gameTimeAllowed value and is decremented by Time.deltaTime in Update().

Time.deltaTime is the time in seconds that the last frame took to complete. Keep in mind that the frame rate may vary and so this value may also vary. When the gameTimeRemaining value is below zero, the timedOut flag is set to true and the player is shown a timeout message.

The code also sets up a gameRunning flag to track if – you guessed it – the game is running. This is useful to stop the countdown logic and you’ll use it later on to control object behavior when
the game is not running.

Attach the script to your Main Camera object:

Game Controller script added to Main Camera.

Play the game and dawdle around until time runs out in order to test the countdown display and the failure case. It’s a little hard to see, but don’t worry, you’ll change that soon. Stop the
game.

Game view of time out test.

Sometimes You Win, Sometimes You Lose

You should display a success message when the Heroic Cube crosses the finish line, or rather the invisible wall – it deserves some encouragement for finishing the challenge! The message will
include the time it took to complete the challenge.

You should also let the player know when time’s up!

The best place to set these messages is the GameController script. However, the finish line detection code is in another script: FinishLineDetection.

One way you can handle this is to define a function in GameController that the FinishLineDetection script can call when the player crosses the line. This function can then trigger the desired
message display via the OnGUI() function.

Add two private variables to the GameController script. One will track the time to complete the challenge and the other will flag a successful mission (the following code can go below the other
existing private variables):

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

Then add the following code to the end of the GameController script:

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

missionCompleted = true;
gameRunning = false;

missionCompleteTime =  gameTimeAllowed – gameTimeRemaining;
}

MissionComplete() checks if the game is running. If it is, it sets a private missionCompleted flag to true and the gameRunning flag to false. The time it took to complete the mission is then saved.

Now modify OnGUI() and add the success case (as shown below) to show the time it took to complete the message. The new code goes just after the var text : String = “”; line and alters the
existing if condition:

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!!”;

Switch to the FinishLineDetection script and modify it as follows (the additions are marked with comments):

#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)

The new code is numbered and does the following:

Defines a public variable that points to the GameController script. You’ll assign this shortly.
Calls MissionComplete() in the GameController script to trigger the success case.

Finish Line script GameController variable created.

To assign the gameControllerScript variable, select the Goal GameObject, then select Inspector\Finish Line Detection and click the circular icon next to the Game Controller Script. In the pop-up dialog, select the Main Camera GameObject and close the dialog.

Selecting GameController script variable source.

Finish Line script GameController variable assigned.

Play the game and dash to the finish line, dodging those nefarious blocks. Check that the correct message is displayed when you make it in time.

Game view test of success path.

Stop the game and click Play once more. Test the failure case to make sure that that logic still works.

Transformer-ing the Display Font

You may have noticed, particularly if you have tested the game using Unity Remote or your iOS device, that the font displaying the game messages is very small. This is as good an excuse as any to learn about importing fonts into Unity.

There are a wealth of fonts you can get from sites such as http://dafont.com. The sample you’re going to use was built with the free Transformers Font. Yes, it’s the original font from the

Transformers movie! Maybe your Cube and blocks are hiding alternate personalities. :]

I added the above font (and a few other resources you’ll need later) to a Resources.zip file that you can here.

Download the resource archive and extract its contents. Find the font file and drag the Transformers Movie.ttf into your Project View\Assets folder to import it. Select the Transformers Movie asset in the Project View. The Inspector shows the import settings. Change the Font Size setting to 36. Click Apply.

Font import settings.

You’ve just imported your custom font and it’s ready for use in your project.

Open the GameController script to modify the message font. Define a public variable to set the font:

var gameMessageFont : Font;

Change the font used for display labels by modifying OnGUI(), as shown below:

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

You assign the gameMessageFont public variable to GUI.skin.font to change the font.

Now select the Main Camera GameObject. Assign gameMessageFont to your newly imported font by dragging the font asset into the gameMessageFont variable.

Font assignment in Game Controller script.

Preview the game and verify that the messages are displayed using the new font.

Game view test of new font.

Ah, much better!

It’s Always Play Time!

Next, create an in-game button that will control the start of the game and allow the player to restart after a success or failure. You’ll display the button whenever the game is not running.

Recall that you defined a gameRunning flag that you can use to control the button’s visibility.

To create the play button and related functionality, you must modify the GameController script. First define a private variable that controls the play button text:

private var playButtonText = “Play”;

Then add a new function called startGame() that sets up a new game:

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;
}

Now modify OnGUI() to show the button when the game is not running by adding the following code to the end of OnGUI() (after all the existing code):

// 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();
}
}

Finally, set the gameRunning flag to false. Just modify the existing line for the variable to switch the initial value from true to false:

static var gameRunning : boolean = false;

OnGUI() places a button using the GUI.Button() function. The button text is a variable, so it says “Play” initially and “Play Again” every subsequent time.

GUI.Button() is wrapped in an if statement. This returns true if the button is clicked. When the user clicks the button, you kick off the game. startGame() first initializes the game, changes the
play button text and finally sets the gameRunning flag to true.

Preview the game in the Unity Editor. Verify that the play button is initially visible and is hidden after you click on it. Verify also that when a run is completed, the play button becomes visiblonce again and that the text has changed from “Play” to “Play Again”. Note that each time you click on the play button after the first time, the time is reset and the countdown starts afresh.

But also notice that the player can move even before the play button is tapped. That’s a little annoying, isn’t it? Don’t let your Heroic Cube get a head start!

To take care of that detail, make use of the gameRunning variable, which is a global variable by virtue of the static modifier. Add the following code to the top of Update() in the MoveAround

script:

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

You should also disable the launcher from dropping obstacles when the game is not running. Add the following code at the top of Update() in the ObstacleLauncher script:

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

Preview the game to ensure that when the game is not running, the player can’t move and obstacles are not launched.

Every Cube Deserves a Fresh Start

While the play button works correctly now, you may notice that even though the timer is reset when a new game is started, other aspects of the game are not reset.

The obstacles stop falling, as they’ve probably reached the maximum spawn count, but they don’t disappear. Nor does the Heroic Cube revert back to its original position. A true reset requires that the game start fresh, i.e., the obstacles are cleared and reloaded and the player position is reset.

The ideal way to accomplish this is to send a message to all interested parties to reset themselves. The Heroic Cube would then slink back to its original position and the launcher would reload.

One way to do this is to have a reset function defined in a script that handles the reset behavior. GameObject.SendMessage() can then be used to call this reset function in the hope that a component (a script) attached to the GameObject handles the function call.

Here’s how you’ll implement this:

You’ll define a function called resetGame() in the MoveAround script that resets the player’s position to the original position when the game started.

You’ll define a function called resetGame() in the ObstacleLauncher script to reset the obstacle count to zero.

You’ll loop through a given list of GameObjects that include the player and launcher GameObjects, calling GameObject.SendMessage(“resetGame”, …) to initiate the reset.

You’ll add the reset logic in GameController when the user resets the game.

But code speaks louder than words. First, open the MoveAround script and add the following variables and functions to it:

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;
}

Then open the ObstacleLauncher script and add this new function:

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

Next open the GameController script and add the following variable:

var gameObjectsToReset : GameObject [];

The above line defines an array of GameObjects that will have the resetGame function called on them to initiate a reset.

Now replace the existing startGame() function with the following (or just update the code to match):

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;
}

The new code clears out all enemy instances by looking for GameObjects with the Enemy tag. Destroy() is called on the enemy GameObjects. This clears out the obstacles in the scene.

Then the code processes the gameObjectsToReset array and sends a message to each GameObject to call resetGame(). It is not mandatory for a component in the array to have implemented resetGame().

You need to assign the objects to process.

Now select the Main Camera object and note the new public variable for Game Objects To Reset:

Game Objects To Reset variable created.

Set the Size to 2. The array elements will expand. Assign the Player GameObject to Element 0 and the Launcher GameObject to Element 1.

Game Objects To Reset array variables assigned.

Preview the game and verify that the game fully resets itself after a success or failure. Verify that the player position resets to the original one, that the obstacles are cleared, and that the obstacles start falling once more when the game is restarted.

This Message Won’t Self-Destruct

Your basic gameplay functionality is now complete, but it would be a nice additional touch to show some info about the game when it’s first launched. All you’re currently showing is a play button. The user doesn’t know what they’re in for.

Adding some welcome text and a super-short explanation of what the game’s about will make it a lot more user-friendly. :] The welcome text will use the Transformers font you imported. For the description text, you’ll use the Arial font that is bundled with Unity.

Create an empty game object, name it Intro and set the Transform Position to 0,0,0.

Select GameObject\Create Other\3D Text to create a 3D Text GameObject and name it Description Text. Set the Inspector\Text Mesh\Text property to “Get to the finish line before time runs out.” Set the initial Transform Position to -10,1,12.

Description text adjustments.

Preview the game with the Unity Editor and adjust the position of the object so that it’s horizontally centered and you can see it clearly.

Hint: You’ll likely have to play around with the x and z positions: x to center to object, and z to zoom in and out.

Description text position adjustments.

Check out the game with Unity Remote as well and make sure the text is visible when viewed on an iOS device. Make any necessary adjustments.

Place the Description Text object under the Intro GameObject. You’re doing this so you can later show or hide the menu display info easily via code.

Description text parented.

Create a second 3D Text GameObject and name it Welcome Text. This text should appear above the description text, so set the initial Transform Position to -6,5,10. Set the Inspector\Text Mesh\Text property to “Welcome”.

Set the Font property to Transformers Movie by dragging that font asset from the Project View and into the Font property in the Inspector (or by tapping the circle with a dot icon next to Font and selecting it from the pop-up list):

Welcome text transform and material modifications.

Adjust the position of Welcome Text so that you can see it clearly when you test the game on both the Unity Editor and through Unity Remote.

Welcome text position adjustments.

Place the Welcome Text object under the Intro GameObject.

You want to hide the Intro GameObject (and its child objects) when the game is running. Open the GameController script and make the following changes:

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

Here you add a new public variable to get a handle to the Intro GameObject. Then you modify startGame() to make the Intro object invisible by turning off the renderers for its child GameObjects.

Now set the Intro variable by selecting Main Camera and dragging the Intro GameObject from the Hierarchy View to the Inspector\Game Controller\Intro variable to assign it. Or use the circle dot icon, since it’s easier.

Intro transform assigned to Game Controller script.

Preview the game to test that the text is hidden when the play button is clicked and the game begins.

Every Brave Cube Deserves a Soundtrack

Audio plays a big part in the gaming experience, both by providing sensory feedback and creating mood. You’ll add audio to further enrich the gameplay.

Sound effects will be triggered when the player crosses the line in time, fails their mission, or when an obstacle hits the ground or bumps into anything. And of course, the game must have some background music! :]

Adding audio with Unity involves attaching an Audio Source component to a GameObject. The Audio Source component has an Audio Clip property that you can assign to the sound you wish to play. The component has additional properties that can control when the clip should be played and whether it should loop. The supported audio file formats include .AIF, .WAV, .MP3, and .OGG.

The following two sites provided the royalty-free music that’s used in this tutorial:

http://incompetech.com/

http://freesound.org/ (registration required to download sounds)

The Resources.zip file you downloaded earlier contains all the audio files that you’ll be using. Feel free to create your own audio effects instead of using the ones I’ve provided. :]

For your reference (and for the sake of attribution), the original links to the audio files included in the Resources.zip file are as follows:

victory: 20784__wanna73__crowd-cheering-football-01.wav
defeat: 83916__timbre__benboncan-sad-trombone-tweaked.wav
impact: 30668__hardpcm__hardbassdrum002.wav
background: Gustav Sting.mp3

Do note though that the files in the Resources.zip file have been renamed for the sake of clarity and brevity. Go to the folder where you originally extracted Resources.zip and import the audio
files by dragging them into your Project View\Assets folder.

Victory audio import settings.

When an audio file is imported into Unity, you can specify whether it should be compressed or remain as-is, i.e., native. (But note that MP3 and Ogg Vorbis audio are always imported in the compressed format.)

Why does this matter? Compressed files tend to be smaller, but they need to be decompressed as the game runs, taking up CPU cycles. You generally want to compress background music. For short sound effects, native is better and tends to provide better sound quality.

If the audio format is compressed, you can choose whether to handle the decompression using hardware, e.g., Apple’s hardware codec if running on an iOS device. Hardware is faster, but the hardware can handle one compressed music file at a time.

You can also mark sounds as 3D. This means that when the sound is played, the effect will be relative to the 3D position of the GameObject. For example, if the GameObject is far away, the sound will be quieter.

Background audio import settings.

Select the background audio in the Project View to show the Import Settings. Unselect the 3D Sound option. Select Hardware decoding. Click Apply to save the setting changes.

The other audio files are .WAV files and you do not need to modify the default Import settings, which should be set to 3D and native audio format.

For sounds to be heard, your scene needs an Audio Listener component to be added to a GameObject. There can only be one Audio Listener in the scene. The listener will pick up sounds from the audio sources close to it and send it to the device speaker.

By default, an Audio Listener is attached to the Main Camera. You can leave it there or attach it to a different GameObject: the player, for example.

In this game, you’ll keep the Audio Listener on the Main Camera, but you can experiment with the different options when you build your own games.

The Sounds of Victory and Defeat

You’re going to attach audio to the Goal GameObject that simulates a crowd cheering or jeering at the finish line.

Select the Goal GameObject in the Hierarchy View and add an audio source by selecting Component\Audio\Audio Source. Set the victory audio asset to the Inspector\Audio Source\Audio Clip property.

De-select the Play on Awake option.

Audio source added to Goal object.

Now create a new JavaScript asset that will be used to play either a victory sound or a defeat sound. Name the new script FanReaction. Open the new script, remove the stub functions and add the following code:

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)

The script takes in two audio clips, one for victory and one for defeat. The playSoundOfVictory() function first stops any audio that’s currently playing, then plays the required audio based on
the isVictory input.

The resetGame() function stops any audio that’s playing. You’ll shortly wire up the GameController to call resetGame() every time the game is restarted.

Attach this new script to the Goal GameObject. Set the victory audio asset to the Audio Victory variable. Set the defeat audio asset to the Audio Defeat variable.

Fan Script added to Goal object.

Edit the GameController script and make the following changes:

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;
}

The code defines a new public variable that references the FanReaction script. You modify MissionComplete() to call playSoundOfVictory(), passing in true to play the victory sound. You also modify Update() to call playSoundOfVictory(), passing in false to play the defeat sound.

You can now link the FanReaction script in the Goal GameObject with the variable in the Main Camera‘s GameController script component. Select Main Camera and then click on the circular icon next to the fanReactionScript variable under the GameController component in the Inspector. In the dialog that pops up, select the Goal GameObject, then close the pop-up.

Fan Reaction script assigned to Game Controller script.

To call resetGame() in FanReaction, select the Main Camera Object. In the Game Controller component section in the Inspector, increase the Game Objects To Reset array size from 2 to 3. Set the Goal GameObject to Element 2.

Goal added to Game Objects To Reset array.

Preview the game and test out the victory and defeat scenarios to make sure the game plays the correct sounds. Verify that the sounds are stopped when you hit Play Again.

Thud in 3D

It would also be nice to have some sort of a sound when obstacles hit the ground. To achieve this, you’ll attach an audio source to the Obstacle Prefab, then detect collisions so you can play the impact audio when the obstacles fall or bump into anything.

Add an Audio Source component to the Obstacle Prefab. Assign the impact audio to the Audio Clip property.

Impact audio added to Obstacle Prefab.

Create a new JavaScript asset and rename it to ObjectCollision. Edit the script, delete the stubbed functions and add the following code:

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

@script RequireComponent(AudioSource)

The code implements the predefined OnCollisionEnter() event function and calls the audio.PlayOneShot() function to play the impact audio clip. audio.PlayOneShot() illustrates another way to play audio, allowing you to pass in the audio you wish to play.

Attach the script to the Obstacle Prefab. Set the impact audio asset to the Impact variable in the script.

Object Collision script and audio variable added.

Preview the game and verify that you hear a pleasing thud sound when obstacles hit the ground or another object. Note that closer the obstacles are to the player, the louder the sounds.

A Little Cube Music

Your game’s almost complete. But there’s one thing missing – some background tunes.

Music does a lot to set the mood for a game. It can get a user’s adrenaline flowing and help them “feel” the game environment by providing contextual clues like bird sounds or wolves howling. So add some music!

Add an Audio Source component to the Main Camera GameObject. Set the background audio asset to the Audio Clip property.

Select the Play on Awake and the Loop options. These ensure that the background music starts as soon as the game starts and that it will play continuously.

Adjust the volume property to 0.1 so it doesn’t drown out the other sounds. If you’re using your own sounds, tweak the volume level depending on your music’s default volume level to achieve the same goal. Users should be able to hear all the sound effects while the background music is playing.

Background audio source added.

Preview the project in the Unity Editor. When completely satisfied, deploy the project to your iOS device. You’ll need to add the Level_3 scene in the Build Settings.

Build settings when adding final level.

Test out the gameplay on your iOs device while you enjoy the sounds you’ve added.

Completed game running on iOS.

Your Heroic Cube has a soundtrack to glorify its bravery!

Where To Go From Here?

Congratulations, you’ve made it to the end of this whirlwind walkthrough of the basics of Unity! You’ve shown as much verve and vigor as your Heroic Little Cube. This could be the beginning of a wonderful journey with Unity gaming.

Here are the source files with all of the code from this tutorial series: Unity Project, Xcode Project.

Believe it or not, you’ve just scratched the surface – there’s a whole lot more to learn. Stay tuned for an upcoming intermediate tutorial series that will take you to the next level with Unity!

In the meantime, be sure to stop by the forums with your questions and feedback, and have fun building awesome games!(source:raywenderlich


上一篇:

下一篇: