转自Unity 3D for iOS

这篇文章还可以在这里找到 英语

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

这份教程是由教程团队成员 Christine Abernathy, 他是Facebook的开发支持团队的工程师。
欢迎来到第三部分,这是Unity 3D for iOS初级系列教程的最后一个部分!
在这个系列的第一部分,你参观了基本的Unity工具,创建了带有一个简单角色控制机制的游戏,并且学习了如何部署你的项目到iOS上。

然后在这个系列的第二部分,你增强了你英勇的小方块的移动,并且给这个世界带来一些生命,它拥有了天空,草地和一个起伏的地形。

在第三部分,也是最后一部分,你将要添加游戏玩法到项目中。替代简单的绕着场景乱转,你英勇的小方块需要在一个给定的时间里,冲刺穿过一个终点线。
要给你的角色一些挑战,在方块以z字路线通向终点的路上,障碍物会像雨点一样落向方块。一个倒计时的时钟添加到剧本中。成功会伴随欢呼 – 失败则是震耳欲聋的沉默.:]
你也几乎接近终点线了,所以记住 – 我行我素!(“Huey Lewis And The News”中的一首歌名)

开始吧:看到尽头了!

首先,保存Level_2场景作为一个新的场景,命名为Level_3. 你在这个教程的第三部分所做的所有修改都保存在这个新的场景中。
你会用两个圆柱和一条连接它们的粗线来创建一条终点线,这样玩家可以清楚的看见目标的方向。这个终点线包括一个不可见的墙,它被用来当你英勇的小方块穿过终点线的时候的触发器。
选择GameObjectCreate Empty来创建一个新的物件来代表终点线。这是一个父GameObject, 在它之下,你可以添加圆柱,线和一堵墙。
通过Inspector面板,将这个物件重命名为Finish Line,或者右击这个物件,然后选择Rename. 设置Transform Position到0,0,0.
要创建第一个圆柱,选择GameObjectCreate OtherCylinder。重命名它为Post1.设置Transform Scale为1,3,1. 设置Transform Position为-10,0,20,把它放在玩家的前面靠左边。用Move工具,调整y位置,让圆柱型物件的底部比地面略微高一些。

提示:从z轴视角观察场景会帮助你方便的做这样的调整。


拖动Post1 GameObject,然后把它放到Finish Line GameObject下面,设置后者作为Post1的父对象。

要创建第二个圆柱,选中Post1,右击然后选择Copy. 再次右击然后选择Paste. 从Post1到Post2重命名新的GameObject. 使用Move工具调整新坐标,让这个圆柱到玩家的右侧。

提示:从y轴观察场景(从上面),会对你的调整有帮助。另一个可选择的方式是,设置Transform x position到10,也应该达到这个目的。

下一步,创建墙来帮助你监测当终点线被穿过的时候。选择GameObjectCreate OtherCube,然后重命名它为Goal.设置
Transform Scale为24,10, 0.5.设置初始的Transform Position为0,2,0

移动这堵墙到两个圆柱后面,如果你需要,调整x缩放的值来让这堵墙从一个圆柱扩展到另一个圆柱。

让这堵墙仍然被选中,打开InspectorBox Collider组件,然后勾选Is Trigger的值。不要勾选Mesh Renderer component让这堵墙不可见。

拖动Goal GameObject,然后把它放置在Finish Line GameObject的下面,让Finish Line GameObject作为父物件。

连点成线

下一步,你会创建一条连接圆柱的线,让玩家可以清楚的看见终点线。你将要通过脚本从一个圆柱到另一个圆柱画一条线来完成这个动作。
选择AssetCreateJavaScript来创建一个新的脚本,然后命名它为FinishLineRender.在项目视图里通过双击打开这个脚本。删除自动生成的函数,然后添加下面的代码:

// 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脚本界面组件,然后你设置这条线的第一个和第二个点到捆绑到两个圆柱的输入变量的值。你设置renderer的材质。
最后你设置从起始点到结束点线的颜色。这个color变量是公共的,所以你可以改变它。
关联这个FinishLineRender脚本组件到Finish Line GameObject上。

提示:你可以通过选择Finish Line物件,然后在Inspector点击Add Compoment来添加这个脚本。这会弹出一个搜索框 – 简单的输入“FinishLineRender”单词的前面几个字母,然后它会显示你要的脚本。


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

在Unity编辑器中预览这个游戏。你应该看见两个目标圆柱和一条绿线穿过它们代表终点线。停止游戏。

下一步,你将会创建一个用来探测终点线被穿越事件的新的脚本,然后会关联这个新的脚本到Goal GameObject上。要完成这个步骤,选择AssetsCreateJavaScript,然后命名这个脚本为FinishLineDetection.
打开这个新的脚本,然后删除自动生成的函数。添加下面的代码:

function OnTriggerEnter(other : Collider) {
 
if (other.gameObject.tag == "Player")
{
Debug.Log("You made it!!!");
}
}
@script RequireComponent(Collider)

你调用OnTriggerEnter()函数,当其他碰撞物进入这个GameObject. 这个游戏对象需要被设置为针对这个事件激活的触发器(你已经为这个Goal物件这样做了)。
角色GameObject有一个Character Controller组件,带有一个碰撞器。所以当玩家跑进Goal GameObject后,OnTriggerEnter()事件会被触发。
在你的代码里,你检查如果进入Goal的GameObject有一个标签命名为”Player”。如果属于这种情况,这个英勇的小方块已经穿过了终点线。
关联这个FinishLineDetection脚本到Goal GameObject上。

提示:在Hierarchy视图中,选中Goal物件,你可以从项目视图中拖动FinishLineDetection脚本到InspectorAdd Component按钮来关联这个脚本到GameObject上。

在你标记这个角色之前,给了这个角色GameObject一个普通,老套的“方块”名字。要保持事情的一致性,重命名这个方块GameObject到Player.

现在,添加一个标签到这个角色物件上,让它可以完成终点线的监测逻辑。在玩家的Inspector中,下拉Tag选择器,然后选择Player.


Player是预先构建的可用的标签之一。以后,你也可以创建你自己的标签来帮助识别游戏中的所有其他的敌人。
点击Play并移动英勇的小方块穿过目标线。你应该看见“You made it!!!”这条日志信息,让你知道你的角色穿过了终点线。

是的,英勇的小方块可以到达目标线,并且赢得这个游戏,但是你不能让它太容易了。你应该提高两级的复杂性到这个游戏里:一个基于时间的挑战和障碍物。首先让我们添加障碍物。

创建发射器

创建一个空的GameObject,然后把它添加到场景中。命名它为Launcher. 这个代表来自邪恶阻挡帝国的发射器发射一些障碍物来阻止英勇的小方块前进。
使用Move工具,在角色和终点线的z轴方向之间放置一个launcher对象,高于玩家。你可以设置transform位置为0,12,8作为起始位置,如果有必要可以微调。

发射器存在的主要作用是发射障碍物,所以你需要给它一些可以发射的东东。
在Unity中通常创建弹药的方式是,通过设计一个GameObject,然后转换为一个可以在场景中实例化的预制件。你将要创建一个障碍物GameObject,将它转变为预制件,然后让Launcher负责发射障碍物到倒霉的角色上。

创建障碍物

创建一个方块GameObject,然后命名它为Obstacle. 设置Transform Scale为2,2,2,因此它要比玩家大一些并且更加吓人一些。这些方块走向了邪恶的一边。:]
给障碍物一个有趣的外观以区别于默认的灰色。要与示例比配,首先从Character Controller包导入一个材质:选择AssetsImport PackageCharacter Controller,然后选择constructor_done材质和下图显示的相关的纹理,最后点击Import

新的材质应该出现在你的项目视图中。

选择Obstacle GameObject.通过修改InspectorMesh RendererMaterialsElement 0属性来改变渲染的材料。点击这个属性边上的原点,会弹出 Select Material对话框。

选择你刚才导入的constructor_done材质。关闭Select Material对话框。

现在你必须标注Obstacle GameObject,这样后面当一个新的游戏开始的时候你可以清除场景中的Obstacle实例。
为了这个目的,创建一个新的标签名叫Enemy. 点击InspectorTagAdd Tag. 标签管理器会出现在面板的右侧。通过点击Tags标签边上的三角展开标签数组。修改Element 0的值从0到Enemy。

选择Obstacle GameObject,然后用新的Enemy标签来标记这个物件。

当Obstacle被实例化的时候,你将要添加的代码希望有一个关联到障碍物上的是一个刚体组件。通过添加一个刚体来完成这步。选择ComponentPhysicsRigidbody(Obstacle仍然被选中):

在项目视图中点击Asset文件夹。通过选择AssetsCreatePrefab创建一个预制件。项目视图显示一个空的预制件。命名它为Obstacle.

注意:如果你有比上面截屏更大的资源图标,并且想知道如何得到资源项的列表视图,简单的方式是向左滑动滑块下面的资源列表.:]

拖动Obstacle GameObject到新的预制件上。

预制件改成了蓝颜色来指示他已经被赋值。
现在你已经创建了这个预制件作为可以复用的资源。在场景中你不再需要它。launcher会接管实例化Obstacle实例的任务,当需要的时候。在Hierarchy视图中,选择Obstacle GameObject, 右击然后选择Delete.

释放海怪…错误,障碍

下一步,通过脚本创建逻辑来完成发射障碍物来这个过程。
创建一个新的脚本资源,然后命名它为ObstacleLauncher.

提示:你可以在项目视图中点击右键,然后选择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到脚本的时候,你可以使用编辑器通过拖动GameObject到脚本的变量上来完成这步。Start()函数代码展示了完成这步的另一种方式。
在Start()中,一个看看那里是否没有目标被赋值的检查。如果没有目标被找到,代码查询一个带有Player标签的GameObject,然后把这个GameObject赋值到目标变量上。
GameObject.FindGameObjectWithTag()函数调用是典型的花开销的调用,因为你需要遍历所有的GameObject.所以你应该想在Start()中调用(它只会被调用一次),而避免把它放到,也就是说,Update()中(它会被调用多次)。
在代码中, Update()首先检查发射器是否已经发射允许发射的最大数量的障碍物。如果不是,它也要检查一个设置的时间间隔是否过了。这步是用来避免在一个较短的时间内发射太多的障碍物。
如果是时候发射另一个障碍物了,那么Obstacle预制件根据发射器的位置和旋转角度被实例化。这个实例化的障碍物然后被已锁定角色前进的方向发射,因此刚好落在角色的前面。
现在保存代码,然后舒展一下筋骨。首先,关联这个ObstacleLauncher脚本到Launcher GameObject. 分配Obstacle预制件到脚本的projectile变量上(你可以从资源列表中拖动预制件到变量上)。分配Player GameObject到脚本的target变量上.

在Unity编辑器中试玩这个游戏,并且检查当英勇的小方块移动的时候在它前面有被发射的障碍物。调整发射器的位置,这样可以让被发射的障碍物在玩家和终点线之间。你也可以通过在z轴远离玩家的方向,移动Finish Line GameObject来做出一样的调整。

提示:你可以设置Transform位置到 0,0,2. 当你移动Finish Line物件的时候,子物件也会跟随,也就是一个父子关系或群组关系的GameObject的作用.


你已经完成了游戏的大部分功能。下一步用一个任务控制脚本来把所有的东西整合在一起,显示游戏的计时器,协调游戏的玩法,和重置场景来开始一个新的游戏。

倒计数

创建一个新的脚本资源,并命名它为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()使用你最初设定的尺寸和当前游戏的消息创建一个a Box Control,这些消息包括倒计时信息,一个胜利的消息或一个失败的消息。
gameTimeAllowed变量代表游戏的时间,并且被设置为20秒。这个gameTimeRemaining变量跟踪当前剩余的时间。它初始被设置为gameTimeAllowed的值,然后在Update()中被Time.deltaTime减掉。
Time.deltaTime是上一帧花费的以秒计的时间。记住帧率会变化的,并且这个值也会变化,当gameTimeRemaining小于零,timedOut标记被设置为true,然后玩家被显示一个超时的消息。
这个代码也设置一个gameRunning来跟踪是否 - 你也猜到了 - 游戏是否在运行。这对于停止倒计时逻辑很有用,并且当游戏不在运行的时候,也会用它来控制物件的行为。
关联这个脚本到你的主摄像头对象:

试玩游戏,然后混一会儿直到时间用完,目的是测试倒计时显示和失败的状况。它非常不容易看的清,但是不要担心,你会马上改变它。停止游戏。

 

有时候你会赢,有时候你会输

你应该显示一个胜利的消息,当英勇的小方块穿过终点线的时候,或者撞上不可见的墙 - 它渴望为完成这个挑战得到一些鼓励!这个消息包含他完成挑战花费的时间。
你也应该让玩家知道时间还剩下多少!
设置这些消息最好的地方是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标记为true,然后gameRunning标志为false. 完成游戏的时间然后被保存下来。
现在修改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()来出发胜利的情况。
 

要分配gameControllerScript变量,选择Goal GameObject,然后选择InspectorFinish Line Detection,然后点击Game Controller Script边上的原点。选择Main Camera GameObject,最后关闭对话框。

 

试玩游戏,冲向终点线,避开那些邪恶的阻挡物。当你在规定的时间内完成的时候,检查是否显示正确的消息。

停止游戏,然后再次点击Play。测试失败的情况。确保那个逻辑仍然可以工作。

改变显示的字体

你也许注意到,特别是如果你用Unity Remote或者你的iOS设备来测试这个游戏,显示游戏消息的字体非常的小。这是一个很好的机会来学习如何导入字体到Unity中。
在类似这样的网站http://dafont.com,这里有丰富的字体你可以获取到.这里例子中你将要用到的是免费的用于构造<a href="http://www.dafont.com/transformers-movie.font">变形金刚的字体</a>,他最初用在变形金刚电影里!也许你的小方块和障碍物是这个电影里的一个隐藏的角色.:]
我添加上面这个字体(和一些你将会用到的其他的资源)到Resources.zip文件,你可以从<a href="http://cdn1.raywenderlich.com/downloads/DashAndZag_Resources.zip">这里</a>下载。
下载这个资源包,然后解压它的内容。找到字体文件然后拖动这个Transformers Movie.ttf到你项目视图的Assets文件夹下面,导入进去。在项目视图中选择Transformers Movie资源。用Inspector查看导入的设置。改变字体大小的设置到36.点击Apply.

你已经导入你的自定义的字体,并且在你的项目中准备好使用它们了。
打开 GameController脚本来改变消息的字体。定义一个公共变量来设置字体:
var gameMessageFont : Font;
通过改变OnGUI()来改变用于显示标签的字体,就像下面展示的那样:
function OnGUI() {
GUI.skin.font = gameMessageFont;
GUI.color = Color.yellow;
...
你通过分配gameMessageFont公共变量为GUI.skin.font来改变字体。
现在选择主摄像头GameObject. 通过拖拽字体资源到gameMessageFont变量来分配gameMessageFont为你新导入的字体。

预览游戏,并检查使用新字体显示的消息。

嗯,好些了!

它总是在计时 !

下一步创建一个游戏中的按钮来控制游戏的开始和允许玩家在胜利和失败后重新开始游戏。当游戏没有运行的时候你会显示这个按钮。回忆你定义过一个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标志为false.只要修改变量存在的那行,调换初始值从true到false:
static var gameRunning : boolean = false;
OnGUI使用GUI.Button()函数来放置一个按钮。按钮的文字是一个变量,所以初始它设置为"Play",每次重新开始前是"Play Again"
GUI.Button()封装在一个if语句中。如果按钮被点击,返回true. 当用户点击按钮,你开始这个游戏。startGame()首先初始化游戏,改变play按钮的文字,最后设置gameRunning标记为true.
在Unity编辑器里预览游戏。检查play按钮初始状态是可见的,当你点击它后隐藏掉了。也核对当一次快跑结束后,play按钮再次出现,而且文字从"Play"变成了"Play Again".注意第一次之后,每次你点击play按钮,时间会被重置,倒计时器会重新开始。
但是也注意到玩家可以在play按钮被点击前就可以移动。这是不是有一点烦人?不要让你英勇的小方块抢跑!
要处理这个细节,要使用gameRunning变量。这个变量是一个全局变量通过静态前缀定义。添加下面的代码到MoveAround脚本的Update()头上:
   if (GameController != null &amp;&amp; !GameController.gameRunning)
return;
你也应该不让发射器,发射障碍物,当游戏还没有开始的时候。添加下面的代码到ObstacleLauncher脚本的Update()头上:
   if (GameController != null &amp;&amp; !GameController.gameRunning)
return;
试玩游戏确认当游戏没有开始的时候,玩家不能移动,障碍物没有被发射。

每个小方块需要一个崭新的开始

现在play按钮工作正常了,你也许注意到及时一个新的游戏开始,计时器被设置了,但是游戏的其他方面没有被重置。
障碍物停止坠落,是因为它们可能到达发射的最大上限,但是它们也没有消失。还有这个英勇的小方块没有回到它原来的位置。一个干净的重置让游戏有一个全新的开始,例如,障碍物被清除和重新加载,角色的位置被重置。
理想的方式来完成这个功能是发送一条消息到所有感兴趣的部分来重置它们自己。英勇的小方块将返回到它原始的位置,而发射器会重新装填。
要做到这点一个方式是在脚本中定义一个reset函数,来重置行为。GameObject.SendMessage()可以调用这个reset函数,希望关联GameObject组件(脚本)可以处理这个函数调用。
这里是你要如何实现的代码:
  1. 你要在MoveAround脚本中定义一个叫做resetGame()函数来重置玩家的位置到游戏开始的初始位置。
  2. 你要在ObstacleLauncher脚本里定义一个叫做resetGame()函数来重置障碍物的数量到零。
  3. 你要循环遍历给定的GameObject列表,包括player和发射器GameObject,调用 GameObject.SendMessage(“resetGame”, …)来调用这个reset.
  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 [];
上面这行定义了一个GameObject的数组,这是一个包含resetGame函数的GameObject数组,依此调用它们来完成一个个重置。
现在替换存在的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标记的GameObejct来清除所有的敌对实例。Destroy()在敌对的GameObject上被调用。这会清除场景中所有的障碍物。
然后代码处理gameObjectsToReset数组,发送每个GameObject一条消息来调用resetGame()函数。它不是强制性的要求数组中的组件有实现resetGame()函数。你需要分配对象来处理。
现在选择主摄像头对象,然后注意公共变量Game Objects To Reset:

设置尺寸为2.数组元素会扩展。分配Player GameObject到 元素0,然后Launcher GameObject到元素1.


试玩游戏,核对在一次胜利或失败后,游戏完全重置了自己。核对玩家位置重置到原始地方,所有障碍物被清除,而且大概游戏重新开始后障碍物开始再次坠落。

消息不会自动消除

你基本的游戏功能已经完成了,但是显示一些关于游戏的信息当它首次加载的时候是一个好的亮点。你现在所有显示的是一个play按钮。用户不知道他们要做啥。
添加一些欢迎的文字和关于游戏的超级简短的介绍会让它更加用户友好.:]欢迎的文字会使用你导入变形金刚字体。对于介绍文字。你会使用和Unity一起打包的Arial字体。
创建一个空的游戏对象,命名它为Intro,然后设置 Transform Position为0,0,0.
选择GameObjectCreate Other3D Text来创建一个3D文字GameObject,然后命名它为Description Text。设置InspectorText MeshText属性为"Get to the finish line before time runs out".设置初始的Transform Position为-10, 1, 12.

用Unity编辑器试玩游戏,然后调整物件的位置让它水平居中,让你可以看的清楚一些。

提示:你会不得不与x和z位置打交道:x用于中间对齐物件,z用于放大和缩小。

<a href="http://www.raywenderlich.com/wp-content/uploads/2012/11/part3_39b_desc_text_adjusted.png"><img title="Description text position adjustments." src="http://www.raywenderlich.com/wp-content/uploads/2012/11/part3_39b_desc_text_adjusted-700x297.png" alt="Description text position adjustments." width="700" height="297" /></a>
用Unity Remote检查游戏,确保文字在iOS设备上显示的出来。对一些必要的调整。
将Description Text放到Intro GameObject下。你这样做的目的是你可以通过简单的代码在后面显示或隐藏显示菜单信息。

创建第二个3D文字GameObject,然后命名它为Welcome Text。这个文字应该出现在描述文字的上面,所以设置初始的Transform Position为 -6,5,10.设置InspectorText MeshText属性为"Welcome".
通过拖动项目视图中的字体资源到Inspector中的Font属性来设置字体属性为Transformers Movie(或者通过点击Font边上带有小点的圆圈,然后选择字体从弹出的列表中):
<a href="http://www.raywenderlich.com/wp-content/uploads/2012/11/part3_42b_welcome_text_transform_text_font.png"><img title="Welcome text transform and material modifications." src="http://www.raywenderlich.com/wp-content/uploads/2012/11/part3_42b_welcome_text_transform_text_font-244x500.png" alt="Welcome text transform and material modifications." width="244" height="500" /></a>
调整欢迎文字的位置,当你在Unity编辑器和Unity Remote中测试游戏的时候,让你可以看的清楚。
<a href="http://www.raywenderlich.com/wp-content/uploads/2012/11/part3_43b_welcome_text_adjusted.png"><img title="Welcome text position adjustments." src="http://www.raywenderlich.com/wp-content/uploads/2012/11/part3_43b_welcome_text_adjusted-700x315.png" alt="Welcome text position adjustments." width="700" height="315" /></a>
将Welcome Text物件放到Intro GameObject下面。
当游戏开始运行的时候,你想要隐藏IntroGameObject(和它的子物件)。打开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 GameObject不可见,通过关闭它的子GameObject的渲染。
现在通过选择主摄像头然后从Hierarchy视图中拖动Intro GameObject到InspectorGame ControllerIntro变量上来设置Intro变量。或者使用圆圈点图标,因为它更加容易.:]

创建第二个3D文字GameObject,然后命名它为Welcome Text。这个文字应该出现在描述文字的上面,所以设置初始的Transform Position为 -6,5,10.设置InspectorText MeshText属性为”Welcome”.

通过拖动项目视图中的字体资源到Inspector中的Font属性来设置字体属性为Transformers Movie(或者通过点击Font边上带有小点的圆圈,然后选择字体从弹出的列表中):

调整欢迎文字的位置,当你在Unity编辑器和Unity Remote中测试游戏的时候,让你可以看的清楚。

将Welcome Text物件放到Intro GameObject下面。

当游戏开始运行的时候,你想要隐藏IntroGameObject(和它的子物件)。打开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 GameObject不可见,通过关闭它的子GameObject的渲染。

现在通过选择主摄像头然后从Hierarchy视图中拖动Intro GameObject到InspectorGame ControllerIntro变量上来设置Intro变量。或者使用圆圈点图标,因为它更加容易.:]

试玩游戏来测试当play按钮被点击,游戏开始后文字隐藏起来了。

每个勇敢的小方块都需要一个音轨。

音乐在游戏的体验中扮演一个重要的角色,及提供感官的反馈也创造情绪。你将要添加音频来增强游戏性。

声音效果会被触发,当玩家按时通过终点线的时候,或者他们的任务失败,或者障碍物撞击到地上,或者碰到了任何东西。当然,游戏也需要一些背景音乐!:]

用Unity添加音频涉及关联一个Audio Source组件到一个GameObject上。这个Audio Source组件有一个Audio Clip属性让你可以分配你想要播放的声音。这个组件有其他附加的属性来控制声音是可以播放,也可以循环。支持音频格式包括.AIF, .WAV, .MP3, and .OGG.

下面两个网站提供免费的音乐,可以被用在这份教程中:

你先前下载的Resources.zip文件包括了所有你会用到的音频文件了。你可以自由创建你自己的音效来替代那些我提供给你的. :]

作为你的参考(并且为了找到原始的链接),在Resources.zip文件中包含的音频文件的原始链接如下:

注意,在Resources.zip文件,为了清晰和简洁,文件已被重新命名。到你最初解压Resources.zip的文件夹下,然后通过拖动它们到你的Project ViewAssets文件夹下来导入音频文件。

当音频文件导入到Unity中,你可以指定是否压缩或保持现状,例如,自然的状态(但是注意MP3和Ogg Vorbis音频总是以压缩的格式导入的)

为什么会这样呢?压缩文件会更加小,但是它们需要在游戏运行的时候解压,会消耗CPU周期。你通常想要解压背景音乐。对于短的音效,自然状态会更好,并且可以提供更好的声音效果。

如果音频格式是压缩的,你可以选择是否通过硬件来处理解压,例如,Apple的硬件解码器,如果运行在iOS设备上。硬件会更快,但是硬件只能同时处理一个压缩的文件。

你也可以让声音有3D效果。意思是当播放声音的时候,效果会与GameObject的3D位置有相关性。举例来说,如果GameObject远离了你,声音会变得轻一些。

在项目视图中选择背景音乐,显示导入的设置。不要勾选3D Sound选项。选择 Hardware decoding。点击Apply来保存改变的设置。

另外一些音频文件是.WAV 文件,你不需要改变缺省的导入设置,也就是应该设置为3D和自然的音频格式。

对于被听到的声音,你的场景需要一个Audio Listener组件添加到一个GameObject中。场景里只有一个Audio Listener。这个收听者会从最靠近它的声音源捡起声音,然后把它发送到设备的喇叭上。

缺省情况下,Audio Listener关联到主摄像头。你可以把他放在那里或者关联它到一个不同的GameObject:例如,你的角色上。

在这个游戏中,你保留Audio Listener在主摄像头上,但是你可以体会一下不同的选择当你构建你自己的游戏的时候。

胜利和失败的音效

你打算关联音频到Goal GameObject上来模拟一个在终点线的观众欢呼或嘲笑。

在Hierarchy视图中选中Goal GameObject,然后通过选择ComponentAudioAudio Source来添加一个音频源。设置victory audio资源到InspectorAudio SourceAudio Clip属性。不要勾选在Awake播放的选项。

现在创建一个新的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资源到Audio Victory变量。设置defeat audio资源到Audio Defeat变量。

编辑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,传递true来播放胜利的声音。你也要修改Update()来调用playSoundOfVictory(),传递false来播放失败的声音。

在主摄像头的GameController脚本组件的Goal GameObject带有一个变量来连接FanReaction脚本。在Inspector中选择主摄像头,然后点击GameController组件下面的fanReactionScript变量边上的圆圈点。在弹出的对话框下,选择Goal GameObject,然后关闭弹出的对话框。

要调用FanReaction中的resetGame(),选择主摄像头物件。在Inspector的Game Controller组件栏目,增加Game Object到Reset数组,从2到3.设置Goal GameObejct到Element 2.

试玩游戏,并且测试胜利和失败的场景,来确认游戏播放了正确的声音。检查声音是否停止,当你再次点击了Play按钮。

在3D环境下的轰的一声

当障碍物撞击到地面的时候,有某种声音也是不错的。要实现这点,你会关联一个音频源到Obstacle预制件,然后当障碍物落下或碰撞到任何其他东西的时候,你可以通过监测碰撞来播放碰撞的声音。

添加一个音频员组件到Obstacle预制件。分配impact audio到Audio Clip属性。

创建一个新的JavaScript脚本资源,然后命名它为ObjectCollision.编辑这个脚本,删除预先添加的函数,然后添加下面这些代码:

var impact : AudioClip;
function OnCollisionEnter () {
audio.PlayOneShot(impact);
} @script RequireComponent(AudioSource)

这段代码实现了预先定义的OnCollisionEnter事件函数,它调用audio.PlayOneShot()函数来播放撞击的音频。audio.PlayOneShot()展示了播放音频的另一种方式,允许你传递你想要播放的音频。

关联脚本到Obstacle预制件上。设置impact audio资源到脚本中的Impact变量。

试玩游戏,并检查当障碍物撞击地面或其他物件时你是否听到一个可喜的砰的一声。注意离障碍物玩家越近,声音越大。

小方块的音乐

你的游戏几乎完成了。但是那里还有一件事漏掉了 – 一些背景音乐。

对于游戏音乐制造了很多气氛。它可以让用户的肾上腺素流动,并且通过如鸟的声音或狼的嚎叫,帮助他们提供游戏环境的线索。所以添加一些音乐吧!

添加一个Audio Source组件到主摄像头。设置background audio资源到Audio Clip属性。

勾选Play on Awake和Loop属性。这些确保当游戏开始后背景音乐就开始播放,并会持续的播放。

调整volume从0.1直到它没有淹没了其他声音。如果你使用你自己的声音,根据你音乐缺省的音量级别微调音量级别来完成同样的目标。用户应该可以通道所有音效当背景音乐播放的时候。

在Unity编辑器里试玩这个项目。当完全满意后,部署这个项目到你的iOS设备上。你需要添加Level_3场景在 Build Settings中。

在你的iOS设备上测试整个游戏,直到你满意你添加的声音。

你英勇的小方块有了背景音乐来鼓舞它的士气。

向哪里进发?

恭喜,你已经到达到了Unity基础旋风演戏的结尾!你已经展示了你英勇小方块的活力和生机。这或许是Unity游戏精彩之旅的开始。

这里是这个系列教程的所有代码的源文件: Unity ProjectXcode Project.

相信或不相信,你只是知道了一些皮毛而已 – 那里还有很多东西要学。敬请关注马上就会来的中级教程系列,那带你到Unity一个下的级别。

在此期间,记得要通过论坛提出你的问题和反馈,来构建充满乐趣的精彩游戏吧!

这份博文是由教程的团队成员 Christine Abernathy,来自Facebook的开发支持团队的工程师。

Unity3D for iOS初级教程:Part 3/3的更多相关文章

  1. Unity3D for iOS初级教程:Part 2/3

    转自Unity3D for iOS 这篇文章还可以在这里找到 英语 Learn how to use Unity to make a simple 3D iOS game! 这篇教材是来自教程团队成员 ...

  2. Unity3D for iOS初级教程:Part 3/3(上)

    转自:http://www.cnblogs.com/alongu3d/archive/2013/06/01/3111738.html 欢迎来到第三部分,这是Unity 3D for iOS初级系列教程 ...

  3. Unity3D for iOS初级教程:Part 1/3

    转自Unity 3d for ios 这篇文章还可以在这里找到 英语 Learn how to use Unity to make a simple 3D iOS game! 这篇教材是来自教程团队成 ...

  4. Unity3D for iOS初级教程:Part 3/3(下)

    转自:http://www.cnblogs.com/alongu3d/archive/2013/06/01/3111739.html 消息不会自动消除 你基本的游戏功能已经完成了,但是显示一些关于游戏 ...

  5. Unity3D for iOS初级教程:Part 1/3(下)

    转自:http://www.cnblogs.com/alongu3d/archive/2013/06/01/3111735.html 一个手指来统治他们 但是等等,你还没有完全完成! 如果你玩游戏有一 ...

  6. Unity3D for iOS初级教程:Part 1/3(上)

    转自:http://www.cnblogs.com/alongu3d/archive/ 如果图片看不到,请查看原文 这篇教材是来自教程团队成员 Christine Abernathy, 他是Faceb ...

  7. Unity3D新手入门初级教程

    根据游戏调查公司 Newzoo 针对全球手机游戏市场所做的调查报告显示,2016年全球游戏市场规模将达到 996 亿美元,其中手机游戏市场将以 21.3% 的增幅获得约 369 亿美元的收入,而中国手 ...

  8. 新手必看,史上最全的iOS开发教程集锦,没有之一!

    最近大火的iPhone XS Max和iPhone XS,不知道有没有同学已经下手了呢?一万三的价位确实让很多人望而却步啊.据说为了赢得中国的用户,专门出了双卡双待的,可想而知中国市场这块“肥肉”人人 ...

  9. [[iso教程]] 《4个月ios实体教程》全网最新、最全ios视频教程

    全网最新.最全ios视频教程 内容简介 <ios实体教程>主要介绍如何使用iOS提供的强大工具集创建iOS应用.全视频对iOS操作系统做了全面的介绍,首先讲解如何构建应用程序的用户界面,涵 ...

随机推荐

  1. SVN的两种存储方式FSFS和BDB比较【转】

    版本库数据存储 在Subversion1.2中,版本库中存储数据有两种方式.一种是在Berkeley DB数据库中存储数据:另一种是使用普通的文件,使用自定义格式.因为Subversion的开发者称版 ...

  2. selenium-WebElement接口常用方法

    1.submit()方法用于提交表单. 例如:在收索框输入关键字之后的“回车”操作,就可以通过submit()方法模拟. 例如: from selenium import webdriverdrive ...

  3. Lesson2

    #ifdef __cplusplus #include <cstdlib> #else #include <stdlib.h> #endif #include <SDL/ ...

  4. Solr版本安装部署指南

    一.依赖包 1.  JDK 1.6以上 2.  solr-4.3.0.tgz 3.  Tomcat或者jetty(注意,solr包中本身就含有jetty的启动相关内容):apache-tomcat-7 ...

  5. 2018_oakland_linuxmalware

    2018年oakland论文:理解linux恶意软件 论文地址:http://www.s3.eurecom.fr/~yanick/publications/2018_oakland_linuxmalw ...

  6. 第1节 flume:11、flume的failover机制实现高可用

    1.4 高可用Flum-NG配置案例failover 在完成单点的Flume NG搭建后,下面我们搭建一个高可用的Flume NG集群,架构图如下所示: 图中,我们可以看出,Flume的存储可以支持多 ...

  7. [POJ] 2411 Mondriaan's Dream

    Mondriaan's Dream Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 18903 Accepted: 10779 D ...

  8. 在已编译安装nginx上动态添加模块

    一.添加nginx模块 找到安装nginx的源码根目录,如果没有的话下载新的源码 wget http://nginx.org/download/nginx-1.8.1.tar.gz 查看ngixn版本 ...

  9. web前端常用的封装方法

    1.放大镜 //页面加载完毕后执行 window.onload = function () { var oDemo = document.getElementById('demo'); var oMa ...

  10. verilog behavioral modeling --loop statement

    1.forever 2.repeat 3.while 4.for The for statement accomplishes the same results as the following ps ...