不知道上一篇里,大家是不是都做出了跳跃的效果。从这一节开始,我提交了代码到Github,大家如果有什么不明白的地方,可以去看源码。另外我的Github里还有两个以前写的spritekit游戏,大家也可以围观一下。传送门

添加障碍物

首先我们测试一下上一节提到的跳跃的数值,我测试出了一组比较合理的数据,大家可以试用一下。

private float jumpForce = 15;
private float gravity = 1;

接着我们添加一个障碍物的素材到游戏中,然后将素材拖拽到关系树面板,会自动生成一个以该精灵为外观的游戏对象。我们在属性面板中,添加一个Box Collider2D组件,和一个Rigidbody2D组件。点击AddComponent之后,直接输入组件的名字,下面会列出搜索结果,直接点击就可以。

我来解释下这两个组件的作用,大家不明白可以百度一下,网上相应的介绍特别多。collider是设置一个碰撞盒,用来检测碰撞的组件。Rigidbody是刚体盒,表示这个游戏对象的物理属性,比如重量、密度等。据我所知,两个物体如果想检测碰撞,必须至少有一者是刚体,如果两个都只设定了collider没有设定刚体,那么检测不到碰撞。创建collider组件之后,会自动按照当前Sprite Renderer组件中的精灵尺寸,创建一个对应尺寸的碰撞盒,也就是collider组件中的Size属性。刚体组件中,默认使用的是动态的刚体,会受到重力影响,勾选Is Kinematic之后,就变成了静态的刚体,大家可以自己尝试一下选和不选的区别,这里我们使用的是静态刚体。如下图:

图片描述

之后我要向大家介绍一个Unity里很好用的玩具:Prefab。这个单词的意思,是预制体,通俗的来说,就是比如我要造一辆车子,那么Prefab就是一些做好的部件模具,在需要的时候,我可以直接用模具立即创建一个现成的零件来使用。当需要在游戏运行过程中,创建大量相同的物体时,这个东西真的很好用,只需要做一次设置,以后直接拿过来初始化就可以了。

这里我们设置好了一个障碍物,然后拖拽关系面板里的障碍物对象到Assets面板中,一个预制体就做好了~!然后我们删掉关系树中的这个预制体,如果后续需要修改它,直接在Assets目录中选中,就可以在属性面板修改了。

在脚本里挂载预制体Prefab

下面我们需要在游戏过程中,动态创建障碍物对象了。

首先我们在关系面板中,添加一个空对象GameObject,设置其坐标到原点。然后点AddComponet添加一个脚本组件,名字叫CreateObstacle,也就是创建障碍物的意思。在Assets面板中打开这个脚本,下面我们来说一下,如何在代码中链接到上面提到的预制体。

CreateObstacle类中,添加一行代码,然后保存一下
public GameObject obstaclePrefab;
还记得我之前说过吧,public属性的内容,可以在Unity引擎的编辑器的属性面板直接访问到,这里也是同理。预制体的默认类型是GameObject,我们在这里创建了一个预制体对象,但是没有赋值,因为我们会在Unity编辑器中拖拽赋值,告诉脚本我们将使用哪一个预制体。(变量名是我个人习惯,加一个Prefab后缀方便我们自己辨认哪一个是预制体)

回到Unity编辑器,选中这个刚创建的GameObject,会发现脚本组件下面多出了一个属性框(如果没有一般是脚本没有保存或者代码有错误)。如图:
图片描述

然后我们将预制体,也就是右边这个拖拽到这个属性栏里,预制体就链接到脚本中了。注意:精灵和预制体从icon上看起来差不多,不要选错了,如果区分不开,仔细对比这两个对象的属性面板就知道了。同时,如果你拖拽的目标不是GameObject类型,那么是无法拖拽上去的。

之后我们回到Mono,现在可以用预制体创建实例了。

使用计时器

计时器在任何游戏中都非常的常用,下面我们做一个简单的计时器。

首先在obstaclePrefab下面,定义两个变量currentTime和interval,分别表示累计时间和时间间隔。

private float currentTime = 0;
private float interval = 1;

然后在Update函数中添加如下代码:

currentTime += Time.deltaTime;//每一帧计算一下累计时间
if(currentTime > interval){//时间超过时间间隔 触发事件
    currentTime = 0;//重置计数器
    Debug.Log("something happen");//Unity中用来打印日志到控制台的函数
}

这样我们就制作了一个间隔时间为1秒的触发器。在Unity中执行一下,会看到控制台每隔一秒会输出字符串something happen。
图片描述

在我的布局里,Project面板和Console都在下面,Assets文件夹就显示在Project面板中。

动态创建障碍物

下面我们用计时器来做一些事。

删掉打印日志的代码,添加以下代码:
Instantiate(obstaclePrefab, Vector3.zero, Quaternion.identity);
这个你从未见过的函数,是以后会大量使用的一个函数,专门用来将预制体实例化为一个可以用的零件,并添加到场景里。第一个参数是预制体链接的变量,第二个参数是一个初始坐标,第三个参数是初始角度。Vector3.zero表示的是坐标原点,Quaternion那个神马的相当于是角度的原点,也就是不旋转任何角度。

然后我们执行游戏,发现场景里只能看到一个对象,但是关系树面板中却不断生成新的对象,这些对象都叠加到了原点上。因为主角只会原地跑,而障碍物需要随着地面一起运动,那么我们下面就让障碍物自己动起来。

在Assets文件夹里选中预制体,添加一个脚本组件:Moving,双击Moving脚本就会在Mono中打开。因为障碍物是随着地面运动,所以我们将ScrollGround脚本中的这一行拷贝到Moving中的Update函数里:
transform.position -= new Vector3(Time.deltaTime, 0, 0);

当障碍物移动到屏幕外的时候,这个障碍物没有用了,只会白白占用内存,那我们在下面来清理掉屏幕外的障碍物。最终Moving脚本的Update函数如下:

void Update () {
    transform.position -= new Vector3(Time.deltaTime, 0, 0);

    if(transform.position.x < -11.36f){//这个数值可以是任何超过屏幕左边界的一个X坐标 我这里直接使用了一个比较大的值
        Destroy(gameObject);//这个函数用来销毁游戏对象
    }
}

Destroy函数中的参数,如果写gameObject(注意小写首字母),那么指代的就是当前游戏对象的本体,这个函数也可以用来销毁当前游戏对象的某个组件,比如Destroy(gameObject.GetComponent<Rigidbody2D>());,GetComponent函数用来获取一个特定组件,这个我们后面再说。

现在执行一下游戏,可以看到障碍物已经“跑”起来了~!

设定角色的碰撞检测

但是好像哪里不对劲!障碍物应该选一个更合适的位置来创建~我们可以拖拽一个预制体到场景中,来回拖动,看它的坐标值变化,找到一个合适的生成障碍物的初始位置。我这里测出的坐标是:
图片描述
不要忘记测试完坐标值,删掉这个临时放进去的障碍物。

我们把这个坐标值填入CreateObstacle脚本中,替换掉Vector3.zero,变成:
Instantiate(obstaclePrefab, new Vector3(7, -0.24f, 0), Quaternion.identity);

回到Unity编辑器,选中主角,添加一个Box Collider2D组件,用来检测主角和障碍物的碰撞,这里collider的尺寸也自动帮我们设定好了。进入Player脚本,添加一个函数:

void OnTriggerExit2D(Collider2D other) {
    Debug.Log("game over");
}

这里我们使用的是一个触发器函数,当碰撞触发时,这个函数里的代码会被执行。但是执行游戏时,发现这个函数没有被触发,这是为什么呢?因为我们还需要设置碰撞盒为trigger类型。分别编辑主角和障碍物的预制体的Box Collider2D组件,勾选Is Trigger选项。这样这两个对象,就会检测到碰撞事件的触发了。

Is Trigger选项,三言两语不太好说明白。如果你需要做不可穿越的碰撞效果,比如人撞到了墙,那么就可以不用Is Trigger。如果你需要做可穿越的碰撞,比如人碰到了毒雾,那么就需要用触发器的形式来做。这里我都用代码来控制了,所以使用了触发器模式,只是为了检测到碰撞。如果不用触发器模式,那么两个collider都要取消Is Trigger选项,然后使用OnCollisionEnter2D函数来检测,具体的用法大家百度一下即可。



敲键盘的猫
772 声望131 粉丝

一只热爱科技的猫