Unity3D for iOS初级教程:Part 3/3(上)
欢迎来到第三部分,这是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轴视角观察场景会帮助你方便的做这样的调整。
![Post height adjustment. Post height adjustment.](http://cdn5.raywenderlich.com/wp-content/uploads/2012/11/part3_1b_post1_after_height_adjusted-480x206.png)
拖动Post1 GameObject,然后把它放到Finish Line GameObject下面,设置后者作为Post1的父对象。
![Post parenting. Post parenting.](http://cdn5.raywenderlich.com/wp-content/uploads/2012/11/part3_2_post1_parented.png)
要创建第二个圆柱,选中Post1,右击然后选择Copy. 再次右击然后选择Paste. 从Post1到Post2重命名新的GameObject. 使用Move工具调整新坐标,让这个圆柱到玩家的右侧。
提示:从y轴观察场景(从上面),会对你的调整有帮助。另一个可选择的方式是,设置Transform x position到10,也应该达到这个目的。
![Second post position adjustment. Second post position adjustment.](http://cdn1.raywenderlich.com/wp-content/uploads/2012/11/part3_3b_post2_after_xposition_adjusted-480x192.png)
下一步,创建墙来帮助你监测当终点线被穿过的时候。选择GameObjectCreate OtherCube,然后重命名它为Goal.设置
Transform Scale为24,10, 0.5.设置初始的Transform Position为0,2,0
![Goal scale adjustment. Goal scale adjustment.](http://cdn4.raywenderlich.com/wp-content/uploads/2012/11/part3_4b_goal_scale_adjusted-480x234.png)
移动这堵墙到两个圆柱后面,如果你需要,调整x缩放的值来让这堵墙从一个圆柱扩展到另一个圆柱。
![Goal scale and position adjustment. Goal scale and position adjustment.](http://cdn4.raywenderlich.com/wp-content/uploads/2012/11/part3_5b_goal_scale_position_adjusted-480x179.png)
让这堵墙仍然被选中,打开InspectorBox Collider组件,然后勾选Is Trigger的值。不要勾选Mesh Renderer component让这堵墙不可见。
![Goal trigger and renderer modifications. Goal trigger and renderer modifications.](http://cdn4.raywenderlich.com/wp-content/uploads/2012/11/part3_6b_boxcollider_istrigger_meshoff.png)
拖动Goal GameObject,然后把它放置在Finish Line GameObject的下面,让Finish Line GameObject作为父物件。
![Goal parenting. Goal parenting.](http://cdn4.raywenderlich.com/wp-content/uploads/2012/11/part3_7_goal_parented.png)
连点成线
下一步,你会创建一条连接圆柱的线,让玩家可以清楚的看见终点线。你将要通过脚本从一个圆柱到另一个圆柱画一条线来完成这个动作。
选择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”单词的前面几个字母,然后它会显示你要的脚本。
![Finish Line script added. Finish Line script added.](http://cdn4.raywenderlich.com/wp-content/uploads/2012/11/part3_8b_finishline_script_added.png)
分别分配Post1和Post2 GameObject到Post 1和Post 2变量上。
![Finish Line script variables assigned. Finish Line script variables assigned.](http://cdn3.raywenderlich.com/wp-content/uploads/2012/11/part3_9b_finishline_vars_assigned.png)
在Unity编辑器中预览这个游戏。你应该看见两个目标圆柱和一条绿线穿过它们代表终点线。停止游戏。
![Game view with finish line. Game view with finish line.](http://cdn3.raywenderlich.com/wp-content/uploads/2012/11/part3_10b_gameview_finishline-468x320.png)
下一步,你将会创建一个用来探测终点线被穿越事件的新的脚本,然后会关联这个新的脚本到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.
![Player GameObject renamed. Player GameObject renamed.](http://cdn4.raywenderlich.com/wp-content/uploads/2012/11/part3_11_cube_renameto_player.png)
现在,添加一个标签到这个角色物件上,让它可以完成终点线的监测逻辑。在玩家的Inspector中,下拉Tag选择器,然后选择Player.
![Player tag selection. Player tag selection.](http://cdn5.raywenderlich.com/wp-content/uploads/2012/11/part3_12_player_tag_select.png)
![Player tagged. Player tagged.](http://cdn2.raywenderlich.com/wp-content/uploads/2012/11/part3_13_player_tagged.png)
Player是预先构建的可用的标签之一。以后,你也可以创建你自己的标签来帮助识别游戏中的所有其他的敌人。
点击Play并移动英勇的小方块穿过目标线。你应该看见“You made it!!!”这条日志信息,让你知道你的角色穿过了终点线。
![Finish line detection test. Finish line detection test.](http://cdn5.raywenderlich.com/wp-content/uploads/2012/11/part3_14b_gameview_finishline-429x500.png)
是的,英勇的小方块可以到达目标线,并且赢得这个游戏,但是你不能让它太容易了。你应该提高两级的复杂性到这个游戏里:一个基于时间的挑战和障碍物。首先让我们添加障碍物。
创建发射器
创建一个空的GameObject,然后把它添加到场景中。命名它为Launcher. 这个代表来自邪恶阻挡帝国的发射器发射一些障碍物来阻止英勇的小方块前进。
使用Move工具,在角色和终点线的z轴方向之间放置一个launcher对象,高于玩家。你可以设置transform位置为0,12,8作为起始位置,如果有必要可以微调。
![Launcher adjustment. Launcher adjustment.](http://cdn4.raywenderlich.com/wp-content/uploads/2012/11/part3_15b_launcher_adjusted-480x263.png)
发射器存在的主要作用是发射障碍物,所以你需要给它一些可以发射的东东。
在Unity中通常创建弹药的方式是,通过设计一个GameObject,然后转换为一个可以在场景中实例化的预制件。你将要创建一个障碍物GameObject,将它转变为预制件,然后让Launcher负责发射障碍物到倒霉的角色上。
创建障碍物
创建一个方块GameObject,然后命名它为Obstacle. 设置Transform Scale为2,2,2,因此它要比玩家大一些并且更加吓人一些。这些方块走向了邪恶的一边。:]
给障碍物一个有趣的外观以区别于默认的灰色。要与示例比配,首先从Character Controller包导入一个材质:选择AssetsImport PackageCharacter Controller,然后选择constructor_done材质和下图显示的相关的纹理,最后点击Import
![Import package material for obstacle. Import package material for obstacle.](http://cdn1.raywenderlich.com/wp-content/uploads/2012/11/part3_16b_obstacle_material_import.png)
新的材质应该出现在你的项目视图中。
![Material for obstacle imported. Material for obstacle imported.](http://cdn3.raywenderlich.com/wp-content/uploads/2012/11/part3_17b_obstacle_material_after_import.png)
选择Obstacle GameObject.通过修改InspectorMesh RendererMaterialsElement 0属性来改变渲染的材料。点击这个属性边上的原点,会弹出 Select Material对话框。
![Selection of obstacle material. Selection of obstacle material.](http://cdn5.raywenderlich.com/wp-content/uploads/2012/11/part3_18b_obstacle_select_material.png)
选择你刚才导入的constructor_done材质。关闭Select Material对话框。
![Obstacle material assigned. Obstacle material assigned.](http://cdn5.raywenderlich.com/wp-content/uploads/2012/11/part3_19b_obstacle_after_material_added.png)
现在你必须标注Obstacle GameObject,这样后面当一个新的游戏开始的时候你可以清除场景中的Obstacle实例。
为了这个目的,创建一个新的标签名叫Enemy. 点击InspectorTagAdd Tag. 标签管理器会出现在面板的右侧。通过点击Tags标签边上的三角展开标签数组。修改Element 0的值从0到Enemy。
![part3_20_add_enemy_tag Adding an enemy tag.](http://cdn4.raywenderlich.com/wp-content/uploads/2012/11/part3_20_add_enemy_tag.png)
选择Obstacle GameObject,然后用新的Enemy标签来标记这个物件。
![Adding an enemy tag. Adding an enemy tag.](http://cdn3.raywenderlich.com/wp-content/uploads/2012/11/part3_21b_obstacle_tagged.png)
当Obstacle被实例化的时候,你将要添加的代码希望有一个关联到障碍物上的是一个刚体组件。通过添加一个刚体来完成这步。选择ComponentPhysicsRigidbody(Obstacle仍然被选中):
![Rigidbody component added to obstacle. Rigidbody component added to obstacle.](http://cdn4.raywenderlich.com/wp-content/uploads/2012/11/part3_22_rigidbody_added.png)
在项目视图中点击Asset文件夹。通过选择AssetsCreatePrefab创建一个预制件。项目视图显示一个空的预制件。命名它为Obstacle.
![Empty Prefab created. Empty Prefab created.](http://cdn3.raywenderlich.com/wp-content/uploads/2012/11/part3_23b_create_prefab.png)
注意:如果你有比上面截屏更大的资源图标,并且想知道如何得到资源项的列表视图,简单的方式是向左滑动滑块下面的资源列表.:]
拖动Obstacle GameObject到新的预制件上。
![Obstacle assigned to Prefab. Obstacle assigned to Prefab.](http://cdn1.raywenderlich.com/wp-content/uploads/2012/11/part3_24b_obstacle_prefab.png)
预制件改成了蓝颜色来指示他已经被赋值。
现在你已经创建了这个预制件作为可以复用的资源。在场景中你不再需要它。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变量上.
![Launcher variables assigned. Launcher variables assigned.](http://cdn2.raywenderlich.com/wp-content/uploads/2012/11/part3_25b_obstacle_launcher_vars_assigned.png)
在Unity编辑器中试玩这个游戏,并且检查当英勇的小方块移动的时候在它前面有被发射的障碍物。调整发射器的位置,这样可以让被发射的障碍物在玩家和终点线之间。你也可以通过在z轴远离玩家的方向,移动Finish Line GameObject来做出一样的调整。
提示:你可以设置Transform位置到 0,0,2. 当你移动Finish Line物件的时候,子物件也会跟随,也就是一个父子关系或群组关系的GameObject的作用.
![Game view of launcher test. Game view of launcher test.](http://cdn3.raywenderlich.com/wp-content/uploads/2012/11/part3_26b_gameview_test_launcher-700x301.png)
你已经完成了游戏的大部分功能。下一步用一个任务控制脚本来把所有的东西整合在一起,显示游戏的计时器,协调游戏的玩法,和重置场景来开始一个新的游戏。
倒计数
创建一个新的脚本资源,并命名它为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来跟踪是否 - 你也猜到了 - 游戏是否在运行。这对于停止倒计时逻辑很有用,并且当游戏不在运行的时候,也会用它来控制物件的行为。
关联这个脚本到你的主摄像头对象:
|
![Game Controller script added to Main Camera. Game Controller script added to Main Camera.](http://cdn1.raywenderlich.com/wp-content/uploads/2012/11/part3_27b_gamecontroller_added.png)
试玩游戏,然后混一会儿直到时间用完,目的是测试倒计时显示和失败的状况。它非常不容易看的清,但是不要担心,你会马上改变它。停止游戏。
|
![Game view of time out test. Game view of time out test.](http://cdn3.raywenderlich.com/wp-content/uploads/2012/11/part3_28b_gameview_test_timeout.png)
有时候你会赢,有时候你会输
你应该显示一个胜利的消息,当英勇的小方块穿过终点线的时候,或者撞上不可见的墙 - 它渴望为完成这个挑战得到一些鼓励!这个消息包含他完成挑战花费的时间。
你也应该让玩家知道时间还剩下多少!
设置这些消息最好的地方是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)
|
- 定义一个公共的变量来指向GameController脚本。你马上会给它赋值。
- 在GameController脚本中调用MissionComplete()来出发胜利的情况。
![Finish Line script GameController variable created. Finish Line script GameController variable created.](http://cdn4.raywenderlich.com/wp-content/uploads/2012/11/part3_29b_after_gamecontroller_var_added.png)
要分配gameControllerScript变量,选择Goal GameObject,然后选择InspectorFinish Line Detection,然后点击Game Controller Script边上的原点。选择Main Camera GameObject,最后关闭对话框。
|
![Selecting GameController script variable source. Selecting GameController script variable source.](http://cdn4.raywenderlich.com/wp-content/uploads/2012/11/part3_30b_assign_gamecontroller_script.png)
![Finish Line script GameController variable assigned. Finish Line script GameController variable assigned.](http://cdn5.raywenderlich.com/wp-content/uploads/2012/11/part3_31b_gamecontroller_var_assigned.png)
试玩游戏,冲向终点线,避开那些邪恶的阻挡物。当你在规定的时间内完成的时候,检查是否显示正确的消息。
|
![Game view test of success path. Game view test of success path.](http://cdn4.raywenderlich.com/wp-content/uploads/2012/11/part3_32b_gameview_test_success.png)
停止游戏,然后再次点击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.
|
![Font import settings. Font import settings.](http://cdn2.raywenderlich.com/wp-content/uploads/2012/11/part3_33b_font_import_settings_changed.png)
你已经导入你的自定义的字体,并且在你的项目中准备好使用它们了。
打开 GameController脚本来改变消息的字体。定义一个公共变量来设置字体:
|
var gameMessageFont : Font;
|
通过改变OnGUI()来改变用于显示标签的字体,就像下面展示的那样:
|
function OnGUI() {
GUI.skin.font = gameMessageFont;
GUI.color = Color.yellow;
...
|
你通过分配gameMessageFont公共变量为GUI.skin.font来改变字体。
现在选择主摄像头GameObject. 通过拖拽字体资源到gameMessageFont变量来分配gameMessageFont为你新导入的字体。
|
![Font assignment in Game Controller script. Font assignment in Game Controller script.](http://cdn2.raywenderlich.com/wp-content/uploads/2012/11/part3_34b_font_assigned.png)
![Game view test of new font. Game view test of new font.](http://cdn1.raywenderlich.com/wp-content/uploads/2012/11/part3_35c_gameview_font_test.png)
它总是在计时 !
下一步创建一个游戏中的按钮来控制游戏的开始和允许玩家在胜利和失败后重新开始游戏。当游戏没有运行的时候你会显示这个按钮。回忆你定义过一个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 && !GameController.gameRunning)
return;
|
你也应该不让发射器,发射障碍物,当游戏还没有开始的时候。添加下面的代码到ObstacleLauncher脚本的Update()头上:
|
if (GameController != null && !GameController.gameRunning)
return;
|
试玩游戏确认当游戏没有开始的时候,玩家不能移动,障碍物没有被发射。
|
每个小方块需要一个崭新的开始
现在play按钮工作正常了,你也许注意到及时一个新的游戏开始,计时器被设置了,但是游戏的其他方面没有被重置。
障碍物停止坠落,是因为它们可能到达发射的最大上限,但是它们也没有消失。还有这个英勇的小方块没有回到它原来的位置。一个干净的重置让游戏有一个全新的开始,例如,障碍物被清除和重新加载,角色的位置被重置。
理想的方式来完成这个功能是发送一条消息到所有感兴趣的部分来重置它们自己。英勇的小方块将返回到它原始的位置,而发射器会重新装填。
要做到这点一个方式是在脚本中定义一个reset函数,来重置行为。GameObject.SendMessage()可以调用这个reset函数,希望关联GameObject组件(脚本)可以处理这个函数调用。
这里是你要如何实现的代码:
|
- 你要在MoveAround脚本中定义一个叫做resetGame()函数来重置玩家的位置到游戏开始的初始位置。
- 你要在ObstacleLauncher脚本里定义一个叫做resetGame()函数来重置障碍物的数量到零。
- 你要循环遍历给定的GameObject列表,包括player和发射器GameObject,调用 GameObject.SendMessage(“resetGame”, …)来调用这个reset.
- 你要在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:
|
![Game Objects To Reset variable created. Game Objects To Reset variable created.](http://cdn3.raywenderlich.com/wp-content/uploads/2012/11/part3_36b_gameresetarray_added.png)
设置尺寸为2.数组元素会扩展。分配Player GameObject到 元素0,然后Launcher GameObject到元素1.
|
![Game Objects To Reset array variables assigned. Game Objects To Reset array variables assigned.](http://cdn1.raywenderlich.com/wp-content/uploads/2012/11/part3_37b_gamereset_assigned.png)
试玩游戏,核对在一次胜利或失败后,游戏完全重置了自己。核对玩家位置重置到原始地方,所有障碍物被清除,而且大概游戏重新开始后障碍物开始再次坠落。