如何使用Unity制作2.5D游戏(第2部分)

原文出自:http://www.raywenderlich.com/4595/how-to-make-a-2-5d-game-with-unity-tutorial-part-2, 由游戏邦翻译, 转载请注明来自游戏邦

这是关于使用Unity开发一款简单的2.5D游戏教程的第二部分内容。

第一部分教程中我们解析了Unity的使用基础并基于C#语言编写了脚本。我们创造了一款简单的游戏,即你控制着一架能够来回穿梭的飞机并向鲨鱼投射炸弹,以此保护小丑鱼。

在本篇教程的这最后一部分中我们将进一步扩展游戏并完成最后的润色。即我们将添加一些音效和音乐,整合游戏逻辑,并添加更多不同的游戏场景。

如果你未拥有之前的内容,你可以使用我们在教程1中所完成的项目,并在Unity中打开这一内容。你点击Scenes\LevelScene条目,便能够打开之前的场景。

接下来让我们开始更深入地学习Unity并进一步完善游戏!

为游戏添加视觉效果

你可能已经发现,当炸弹击中鲨鱼时,它只会安静地消失,而这会让你感到“并不有趣!”

所以我们便决定创造水中爆破场景!

在菜单中选择“GameObject/Create other/Particle System”,屏幕上便会出现一个颗粒系统。在“等级”面板上将名称从“颗粒系统”改为“爆炸”,并将爆炸位置设为(1,1,8)。

现在你如果已经是颗粒系统专家,可以打开“检查器”自己进行设置,或者你也可以遵循我们的方法轻松地进行设置。将以下数值复制在“检查器”中:

在这里最重要的属性便是“One shot”,当你在这一选项中打勾时,系统将只会发射出一次粒子,也就是说只会出现一次爆炸。接下来让我们设置动画中的数值,即尝试着去匹配下图中的颜色(如果你没有执行这个步骤也没关系):

在这里,“Autodestruct”是一个很重要的属性,当我们再也看不到活跃的粒子时,被选中的颗粒系统将从场景中消失。而这也是我们想要创造出的效果,就像自动垃圾回收一样。

现在你便创造了一个小型的爆破场景,随后你就需要像之前在创造炸弹时那样,制作一个预制件(并在需要时实例化),让它在完成场景后自动毁灭。

右击“项目”面板的“预制件”文件夹,选择“Create/Prefab”,并将其重命名为“ExplosionPrefab”。从“等级”中将“爆炸”对象拖到新的 “ExplosionPrefab”文件夹中。右击“等级”中的“爆炸”并选择“删除”。

右击“项目”面板并选择“Sync MonoDevelop Project”以打开MonoDevelop。加载编辑器BombClass.cs并添加以下代码:

//right under definition of “ySpeed”
public GameObject explosionPrefab;

//inside OnTriggerEnter, right after Destroy(this.gameObject)
Instantiate(explosionPrefab, transform.position, Quaternion.identity);

然后再回到Unity,在“项目”面板中选择“BombPrefab”,这时候你将能够在“检查器”中看到新属性“ExplosionPrefab”。从“项目”中将“ExplosionPrefab”拖到新属性区域中你便算完成了设置。

现在点击播放,你便能够看到炸弹撞击鲨鱼时的爆破。

在游戏中添加音效

光有视觉效果还不够,我们还需要一些音效!
一款游戏怎么能够缺少背景音乐?!这时候我们需要提到另外一个人——Kevin Macleod,他是一名伟大的作曲家,在CC license发布了许多电影音乐和游戏音乐。所以如果你想使用他的作品,就必须明确标示出处。

打开网站:http://incompetech.com/m/c/royalty-free/index.html?keywords=the%20cannery

将《The Cannery》下载到你的唱片中,并将“The Cannery.mp3”拖到“项目”面板中的“音频”文件夹中。

我们同样也希望能够循环播放音乐,而这时候我们需要将音乐附加到何种对象上?

让我们将其附加到摄像机上!摄像机也是一种游戏对象,也能够带有其它附加组件。

从“项目”面板中将“The Cannery”拖到“等级”面板中的“主摄像机”中。在“等级”中选择“主摄像机”并在“检查器”中找到音源带,在“循环”的选择框中打勾并将音量设置为“0.20”。

这个步骤非常简单,现在你可以边看游戏场景并享受背景音乐了。

使用Unity创造图形用户界面(GUI)

让我们开始研究GUI。Unity能够给你提供一些标准标签和按钮,但是这些却不是该工具的最大优势。不过我们将使用标签去呈现当前的分数变化;而首先我们将执行分数逻辑。

切换到MonoDevelop。打开PlayerClass.cs并添加一个新属性:

public static int score = 0;

又出现了一个新的属性——“静态”。当我们加载了一个类并且不管是否会出现类实体该属性都将保持不变时,我们便创造出静态属性。除此之外,我们还能够从其它类中获得这一属性——这也是我们为何始终将在静态属性中计分的原因。

接下来添加以下函数以观察分数的更新(也许现在做来并没有意义,但是还是照做吧)——观察我们是如何通过类名称获得分数属性:

public void updateScoreBy(int deltaScore) {
    PlayerClass.score += deltaScore;
}

在PlayerClass中再添加以下函数,从而让分数计算能够呈现在屏幕上:

void OnGUI() {
    GUIStyle style = new GUIStyle();
    style.fontSize = 20;
    GUI.Label(new Rect(10,10,100,20), “Score:”+PlayerClass.score, style );
}

我们将在GUI图层的每一帧中调用“OnGUI”这一事件处理程序。所以你可以在此画上你在GUI中所需要的一切内容。

GUIStyle便是一种模拟CSS的类,所以你也可以在此使用CSS中的字号,左边距宽等功能,否则你仅仅只能局限于字体大小的设置中。GUI.Label() 是由3个参数构成的函数:标签界限的矩形,绘画线以及文本类型。

这时候我们的任务便是在玩家击中或遗漏时更新分数!打开BombClass.cs并进行如下修改:

//add a new property
public PlayerClass player;  
//replace the existing OnTriggerMethod with
void OnTriggerEnter(Collider obj) {
    if (obj.gameObject.name == “Shark”)
    {
        //reset shark
        obj.gameObject.transform.rotation = Quaternion.identity;
        obj.gameObject.transform.position = new Vector3(20f, -3f, 8f);
        player.updateScoreBy(+1);
        Destroy(gameObject);
        Instantiate(explosionPrefab, transform.position, Quaternion.identity);
    }
    if (obj.gameObject.name == “ClownFish”) 
    {
        //reset fish
        obj.gameObject.transform.rotation = Quaternion.identity;
        obj.gameObject.transform.position = new Vector3(-20f, -1f, 8f);
        player.updateScoreBy(-1);
        Destroy(gameObject);
        Instantiate(explosionPrefab, transform.position, Quaternion.identity);
    }
}

虽然这与我们之前所做的相类似,但是我们拥有一个新的属性,即“玩家”,并且当我们要更新分数时我们将会调用player.updateScoreBy()函数。

同时为了让游戏更加有趣,游戏玩家在击中鲨鱼时能够获得分数,但是如果击中的是小丑鱼就会因此被扣分。如此游戏变得更加复杂了!

最后,我们需要根据炸弹设置玩家属性。不过因为炸弹是动态的,所以我们不能再像以前那样进行设置。但是幸运的是炸弹是由玩家所创造的,所他在创造炸弹的同时也明确了玩家属性。

让我们打开PlayerClass.cs并在“bombObject.transform.position = this.gameObject.transform.position”下面添加以下代码:

BombClass bomb = (BombClass)bombObject.GetComponent(“BombClass”);
bomb.player = this;

这里再次出现了一个新属性。bombObject是一种GameObject,所以我们在此调用“GetComponent”函数,如此我们便能够访问所有游戏对象中的附加组件(我们将结果归为BombClass),最后我们便获得附加于游戏对象中的C#分类的参考内容。这样我们便设置了“玩家”属性(以PlayerClass函数为例)。

这时候我们便能够在游戏场景中看到分数计算。

Unity对象和组件

这时候我们已经使用Unity创造出了游戏对象模式。但是你肯定希望能够更好地理解自己所创造的内容,所以让我们创造一个“短途游程”,观察游戏对象与附加组件之间的关系。

我们看到的所有“条带”内容(也就是附加于游戏对象上的所有组件)都被添加到“检查器”面板上。即使是一个空白的游戏对象也拥有自己的转换带,即位置,旋转和缩放。而其它内容则是附加于游戏中的组件。

打开BombPrefab,你会发现许多不同的组件:

转换:就像上述描写到的,主要是提供位置,旋转和缩放
网格过滤器:提供可视对象的几何图形
网格渲染器:渲染几何图形
刚体:控制物理属性
音源:播放音频
脚本:更新对象程序

但是这些内容还只是关于少数能够附加于对象上的组件。为了帮助理解,我们可以看看以下图表:

所以,从脚本组件的角度来看,我们现在更加明确为何需要调用这一代码了:

Destroy(this.gameObject);

即为了破坏与对象相关的所有内容。而基于gameObject属性我们也能够访问其它组件,所以我们便能够调整物理效果或音量等内容。

添加更多场景

现在我们的游戏变得越来越完整了,但是却玩家却还不能面对胜利或失败。
所以让我们在游戏中添加“你赢了”图像,即当玩家分数超过3分时便会看到这个画面。
在菜单中选择“新建场景”,然后“保存场景”,选择文件夹[your project's directory]/Assets/Scenes,并将场景文件重命名为“WinScene”。

选择“等级”中的“主摄像机”,并设置:位置为(0,0,0),投射为“直线”,规格为“10”,近距离为“0.5”而远距离为“22”。在菜单中选择“GameObject/Create Other/Directional Light”并在检查器中设置位置(0,0,0)。

我们希望在这个场景中设置一架飞机(就像游戏关卡中的背景),并在飞机上方播放“你赢了”的影像,所以让我们再次重复教程第一部分中的内容:打开菜单中的“GameObject/Create Other/Plane”,并在检查器中设置:位置(0,0,8),旋转(90,180,0),缩放(3,1,2)。

下载并将Vicki Wenderlich(独立美术家兼插画家)创作的图像保存在你的磁盘中:

将“gameoveryouwin.png”拖到“项目”面板上的“纹理”文件夹中。在输入纹理后你会发现这时候的画面不是很好看,主要是因为压缩的关系,所以你应该选择“项目”中的“gameoveryouwin”纹理,然后在“检查器”中找到“格式”,将其改为“16bits”,并点击“运用”。现在从“项目”面板中将“gameover_youwin”拖到“等级”中的“飞机”。这时候你将能够在“游戏”面板中看到“你赢了”画面——以及Vicki所绘制的一只浮动在下方的邪恶鲨鱼。

我们希望此时的玩家能够再次开始游戏:右击“项目”面板,并在“分类”文件夹中选择“Create/C Sharp Script”,将其重命名为“GameOverClass”。右击并选择“Sync MonoDevelop Project”。在MonoDevelop中打开新的GameOverClass.cs,并用以下代码取代原有的内容:

using UnityEngine;
using System.Collections;   
public class GameOverClass : MonoBehaviour {
    // Update is called once per frame
    void Update () {
        if (Input.anyKeyDown) {
            PlayerClass.score = 0;
            Application.LoadLevel(“LevelScene”);
        }
    }
}

此时,当玩家轻拍屏幕时,分数将会进行重置,并会重新载入游戏玩法。只要在Application.LoadLevel() 函数中加载场景名称便可,多简单。

再次回到Unity:从“项目”面板中的“分类”文件夹将“GameOverClass”脚本拖到“主摄像机”中。

现在我们只要选择“File/Build Settings”并在弹出窗口中点击“添加current”按钮,关闭窗口,便能够将这一场景整合到项目中。

同时让我们快速添加“你输了”场景!与之前一样,创建一个“新场景”,然后“保存场景”并在场景文件夹中将其重命名为“LooseScene”。

选择“等级“中的“主摄像机”并设置:位置为(0,0,0),投射为“直线”,规格为“10”,近距离为“0.5”,远距离为“22”。打开菜单中的“GameObject/Create Other/Directional Light”并在检查器中设置:位置为(0,0,0)。再选择菜单中的“GameObject/Create Other/Plane”然后在检查器中设置位置为(0,0,8),旋转为(90,180,0),缩放为(3,1,2)。

下载“你输了”影像并将其保存在磁盘中:

为了完成场景设置,我们需要像之前那样进行以下几个步骤:

将“gameoveryoulose.png”拖到“纹理”文件夹的“项目”面板中。
在“项目”中选择“gameover
youlose”纹理,然后在“检查器”中找到“格式”并将其改为“16bits”,并点击“运用”。
从“项目”面板中将“gameover_youlose”拖到“等级”中的“飞机”。
从“项目”面板中的“分类”文件夹将“GameOverClass”拖到“主摄像机”中。
在主菜单中选择“File/Build Settings”并在弹出窗口点击“添加current”按钮,关闭窗口。

在这里你拥有3个场景,而你需要将它们连接起来!

通过在“项目”面板中双击“LevelScene”加载LevelScene场景。切换到MonoDevelop并打开PlayerClass.cs。我们将修改updateScoreBy函数以检查玩家的分数是否超过3分或者低于3分。

//replace the updateScoreBy method with this code
public void updateScoreBy(int deltaScore) {
    PlayerClass.score += deltaScore;
    if (PlayerClass.score>3) {
        Application.LoadLevel(“WinScene”);
    } else if (PlayerClass.score<-3) {
        Application.LoadLevel(“LooseScene”);
    }
}

现在我们便将场景流程设置好了。你可以在Unity中点击播放进行观看。当然了,你也可以点击“File/Build&Run”或者在弹出的Xcode中点击“运行”,甚至你也可以在自己的iPhone上尝试这款游戏了。

最后!2.5D的尝试

是时候该上重头戏了,也就是这系列教程中最关键的部分——2.5D!
而我们将在此添加一些内容让你能够创造出一款接近于3D的游戏。
迄今为止我们都将摄像机的投射设置为“正交”,但是如此会让场景看起来就像是平面的2D游戏,所以让我们停止这么做!

在你的LevelScene场景中选择“主摄像机”,并在检查器中将投射变为“透视”,而“视野”为“100”(以弥补透视)。现在你便可以点击播放按钮观看你的2.5D游戏了!很酷吧!

但是我们的工作还未结束。

我们希望游戏能够变得更加复杂,并演示出摄像机的旋转与移动,每次当玩家分数提高时我们将会基于一个圆弧路径去移动摄像机,并改变其视角。根据这一理念,玩家如果更加深入游戏,便能够以一种更加奇特的视角去看待游戏世界并尝试着炸毁鲨鱼。

切换到MonoDevelop并改变PlayerClass.cs函数:

//add the properties
public GameObject mainCamera;
public GameObject gameBackground;
public float nextZ = 0;
//at the end of the updateScoreBy method
if (PlayerClass.score>0) {
    nextZ = PlayerClass.score*2.5f;
}
//at the end of the Update method
if (nextZ > mainCamera.transform.position.z) {
    mainCamera.gameObject.transform.Translate(
    3* Mathf.Sin(transform.position.z/2 ) * Time.deltaTime,
    0,
    -Mathf.Sin(transform.position.z /2 ) * Time.deltaTime *0.3f
    );
    mainCamera.gameObject.transform.RotateAroundLocal( Vector3.up, Time.deltaTime*0.1f );
    gameBackground.gameObject.transform.RotateAroundLocal( Vector3.up, Time.deltaTime*0.1f );
}

虽然看起来是密密麻麻的代码,但是却都是我们所需要的内容。让我们一点一点理解这些内容。

首先我们需要明确主摄像机和背景飞机的属性。我们将移动并旋转摄像机,同时我们也将旋转背景。

摄像机将从当前的Z轴0点的位置移向7.5Z的位置。所以当玩家每次获得分数时Z轴的设置将被设定为2.5,5.0然后7.5(如此上升)——从这些数值中我们可以看到,主摄像机的视角将转变成反正弦函数中的弧形。

通过Mathf分类我们便能够获得这些数学函数,如Mathf.Sin()函数。同样地我们也可以通过调用transform.RotateAroundLocal旋转摄像机,或者通过一个轴线或角度进行旋转。我们将同时旋转摄像机和背景,如此摄像机就能够始终面对着背景了(也就是说背景永远不会脱离屏幕范围)。

除此之外,我们还需要连接新的公共属性。让我们再次切换到Unity,在“等级”面板中选择“玩家”对象。从“等级”中将“主摄像机”拖到检查器中的新的“主摄像机”属性;从“等级”中将“背景”对象拖到“检查器”中新的“游戏背景”属性。

恭喜,你终于完成了所有的工作!现在点击File\Build并点击运行,你便能够在自己的iPhone上运行完成后的游戏,尽情享受从奇特的角度炸毁鲨鱼的乐趣吧!

使用Unity除错

当你继续自己开发游戏时,你将会遇到一些问题,并且也需要自己去移除这些问题。所以你就必须牢记以下内容:

  1. 不要忘记在工具栏中有一个暂停键,如果你需要中止游戏的执行并检查所有对象属性,你只需要按压这个按键便能够浏览游戏场景,并在“检查器”面板中检查各个数值。
  2. 如果你不确定你的函数是否正确,在控制台上(就像Xcode)输入一段信息。调用Debug.Log(“信息”),并将此信息输入控制台中。你可以选择菜单中的“Window/Console”打开控制台窗口。
  3. 培养这种习惯,即当你在MonoDevelop中编程时记得回到Unity编辑器中检查Unity的状态栏(游戏邦注:在应用窗口的最底部)——因为如果你编写了一个无效的错误代码,你一打开Unity便能够立刻发现红色标注的错误信息。
  4. 如果你的对象不能移动,你就需要反复检查你的脚本是否附加到对象上!
  5. 当你在运行游戏时你也可以在检查器中改变各种数值。当你停止游戏时,检查器中的所有数值都会被重新设置为你之前运行的游戏那样。因此如果你忘记停止游戏并做出改变,你便会遗失那些重要数值。所以你必须确保在完成测试后停止游戏,然后才能继续开发工作。

标签: unity

?>