在前面的教程(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会看到吃豆人可以吃豆豆了: