查看原文
其他

教程分享 | 使用Unity 2D制作经典游戏《吃豆人》(3)

2015-12-30 Unity官方平台
在前面的教程(1)(2)中,我们介绍了如何使用Unity创建《吃豆人》的迷宫,以及添加吃豆人的动画。今天的文章将介绍如何设置吃豆人物理以及添加豆豆。
吃豆人物理
现在吃豆人也只是一张没有物理特性的图片,所以它不会移动。在吃豆人上新加一个Collider使其具有物理性质,然后周围的物体就可以与它进行碰撞而非直接穿过。

吃豆人可以到处移动。Rigidbody可用于处理重力、速度和其它推动它移动的力。物理世界所有可以移动的物体都需要一个Rigidbody组件。

在层次面板中选中pacman,依次点击Add Component->Physics 2D->Circle Collider 2D添加碰撞器,同样依次点击Add Component->Physics 2D->Rigidbody 2D添加Rigidbody。使用下图的设置:
现在吃豆人可以与周围的物体进行碰撞了。这也会导致所有绑定到pacman的脚本中的OnCollisionEnter2D函数被调用。
Movement脚本
现在有几种让吃豆人移动的方法。最简单的就是创建脚本,并检测键盘方向键操作,然后按照对应的方向移动吃豆人。但这种操作不好。我们来试试复杂点的办法。玩家按下某个方向键时,吃豆人都在相应方向移动一个完整单位。豆豆也会按照一个单位的间隔距离来摆放,所以吃豆人每次需要移动一个完整单位。在层次面板中选中pacman,依次点击Add Component->New Script新建C#脚本PacmanMove。将脚本放在项目视图中新建的Scripts文件夹下:
双击脚本会看到如下代码:

using UnityEngine;
using System.Collections;

public class PacmanMove : MonoBehaviour {

// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {

}
}

游戏启动时Unity会自动调用Start函数。Update函数会不停调用,几乎都是每秒60次(基于当前帧率,场景中东西过多可能会有所下降)。还有另一种更新函数叫FixedUpdate。也会不停调用,但时间周期固定。Unity以固定的时间周期来进行物理计算,所以物理相关的内容最好使用FixedUpdate:

using UnityEngine;
using System.Collections;

public class PacmanMove : MonoBehaviour {

void Start() {

}

void FixedUpdate() {

}
}

我们需要一个公共变量以便后面在检视面板中修改移动速度:


using UnityEngine;
using System.Collections;

public class PacmanMove : MonoBehaviour {
public float speed = 0.4f;

void Start() {

}

void FixedUpdate() {

}
}

还需要一个方法来判断吃豆人是否可以朝某个方向前进,或者前面是否有墙。例如,如果想知道前面是否有墙,就从吃豆人前方一单位距离处发出射线到吃豆人,看看是否撞到东西。如果撞到的是吃豆人自身则什么都没有,否则就是有墙。函数如下:

bool valid(Vector2 dir) {
// Cast Line from 'next to Pac-Man' to 'Pac-Man'
Vector2 pos = transform.position;
RaycastHit2D hit = Physics2D.Linecast(pos + dir, pos);
return (hit.collider == collider2D);
}

注:这里只是简单的从距离吃豆人一单位的点 (pos + dir)发射到吃豆人自身(pos)。

还要一个函数让吃豆人朝给定方向移动一单位的距离。只需:
transform.position += dir;

但这看起来太生硬了,因为一步很大。我们想让它更平滑的移动,每次一小步。将目的地保存到变量,然后在FixedUpdate函数中一步步移动:

using UnityEngine;
using System.Collections;

public class PacmanMove : MonoBehaviour {
public float speed = 0.4f;
Vector2 dest = Vector2.zero;

void Start() {
dest = transform.position;
}

void FixedUpdate() {
// Move closer to Destination
Vector2 p = Vector2.MoveTowards(transform.position, dest, speed);
GetComponent<Rigidbody2D>().MovePosition(p);
}

bool valid(Vector2 dir) {
// Cast Line from 'next to Pac-Man' to 'Pac-Man'
Vector2 pos = transform.position;
RaycastHit2D hit = Physics2D.Linecast(pos + dir, pos);
return (hit.collider == collider2D);
}
}

注:使用GetComponent获取pacman的Rigidbody组件用于移动(永远不要用transform.position来移动带有Rigidbody的游戏对象)。

在不移动的时候要注意按下的方向键。
注:当前位置就是目的地时不会移动。

在FixedUpdate函数中加入输入控制:

void FixedUpdate() {
// Move closer to Destination
Vector2 p = Vector2.MoveTowards(transform.position, dest, speed);
GetComponent<Rigidbody2D>().MovePosition(p);

// Check for Input if not moving
if ((Vector2)transform.position == dest) {
if (Input.GetKey(KeyCode.UpArrow) && valid(Vector2.up))
dest = (Vector2)transform.position + Vector2.up;
if (Input.GetKey(KeyCode.RightArrow) && valid(Vector2.right))
dest = (Vector2)transform.position + Vector2.right;
if (Input.GetKey(KeyCode.DownArrow) && valid(-Vector2.up))
dest = (Vector2)transform.position - Vector2.up;
if (Input.GetKey(KeyCode.LeftArrow) && valid(-Vector2.right))
dest = (Vector2)transform.position - Vector2.right;
}
}

注:将transform.position转换为Vector2是与其它Vector2比较或相加的唯一方法。-Vector2.up表示left, -Vector2.right表示down。保存脚本后点击Play可以看到按下方向键后吃豆人会移动:


设置动画参数
现在吃豆人可以在迷宫中四处移动来,但移动的动画不太对。没关系,只需简单的计算移动方向并设置动画参数即可:

void FixedUpdate() {
// Move closer to Destination
Vector2 p = Vector2.MoveTowards(transform.position, dest, speed);
GetComponent<Rigidbody2D>().MovePosition(p);

// Check for Input if not moving
if ((Vector2)transform.position == dest) {
if (Input.GetKey(KeyCode.UpArrow) && valid(Vector2.up))
dest = (Vector2)transform.position + Vector2.up;
if (Input.GetKey(KeyCode.RightArrow) && valid(Vector2.right))
dest = (Vector2)transform.position + Vector2.right;
if (Input.GetKey(KeyCode.DownArrow) && valid(-Vector2.up))
dest = (Vector2)transform.position - Vector2.up;
if (Input.GetKey(KeyCode.LeftArrow) && valid(-Vector2.right))
dest = (Vector2)transform.position - Vector2.right;
}

// Animation Parameters
Vector2 dir = dest - (Vector2)transform.position;
GetComponent<Animator>().SetFloat("DirX", dir.x);
GetComponent<Animator>().SetFloat("DirY", dir.y);
}

注:这里使用基本的向量数学来计算移动方向。只需将目标位置与当前位置相减即可

到此吃豆人的移动就完成了。保存脚本后点击Play可以看到移动动画也正常了:


保证吃豆人在最上层
在制作豆豆之前,要先保证吃豆人在所有物体上层。这是2D游戏,所以这里没有3D游戏中的Z值。也就是说Unity按照它自己的想法来绘制物体。我们要确保吃豆人绘制值所有物体上层。

有两种办法。改变Sprite Renderer的Sorting Layer 属性,或改变Order in Layer 属性。对于有很多对象的游戏来说使用Sorting Layer 更合适,这里我们只要将Order in Layer 改为1就好:
注:Unity会按从低到高的顺序来绘制对象。所以将吃豆人的Order in Layer设为1后,Unity会先绘制Order为0的迷宫、豆豆等。这样吃豆人就可以永远在最上层。
豆豆
吃豆人吃的就是豆豆。就像现在的水果和食物。首先制作一张2x2像素的图作为豆豆。将图片保存到Assets/Sprites 文件夹下,并使用如下导入设置:
然后将它拖拽到场景。我们希望在吃豆人走过豆豆时收到通知,所以在检视面板中依次点击 Add Component->Physics 2D->Box Collider 2D并选中Is Trigger:
注:启用IsTrigger的Collider只会接收碰撞信息,不与其它物体进行物理碰撞。

不论何时在吃豆人或怪物走过豆豆时Unity都会自动调用OnTriggerEnter2D函数。依次点击Add Component->New Script新建C#脚本Pacdot,并将其放到Scripts文件夹下:

using UnityEngine;
using System.Collections;

public class Pacdot : MonoBehaviour {

// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {

}
}

这里不需要Start或Update函数,只需上面提到的OnTriggerEnter2D函数:

using UnityEngine;
using System.Collections;

public class Pacdot : MonoBehaviour {

void OnTriggerEnter2D(Collider2D co) {
// Do Stuff...
}
}

这个函数很简单。就是检测是否有物体走过,如果有就销毁豆豆:

using UnityEngine;
using System.Collections;

public class Pacdot : MonoBehaviour {

void OnTriggerEnter2D(Collider2D co) {
if (co.name == "pacman")
Destroy(gameObject);
}
}

注:如果想在此游戏中获得高分,就要多吃些豆豆。注层次面板中右击Pac-Dot,选中Duplicate并移动到其它空位。注意豆豆的位置都是类似(1, 2) 而不能是(1.003, 2.05)这样。

重复添加豆豆并摆放后结果如下:
点击Play会看到吃豆人可以吃豆豆了:


也可将所有的豆豆都放在迷宫下面:
这样将迷宫折叠后层次面板看起来就比较简洁:

在下篇最终教程(4)中,我们会介绍如何在游戏中添加怪物,请大家继续关注!如想了解更多Unity开发教程,请点击阅读原文,关注Unity中文官方社区!



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存