详细的来为大家剖析一下unity flappy bird,附带源码内容详解。FlappyBird不用详细的废话了,它大家应该都玩过的,是一款极其简单,但是又很活宝的游戏。当我知道这款游戏的时候,就有一种把它重现的冲动,然后花了我4个多小时,写出来了一个可以玩的版本,分享给大家。

下面简单介绍游戏的开发过程(本文的例子需要使用unity4.3.0以上的版本打开)。

目录介绍

 运行图:


项目的目录结构如下图,anims中存放动画资源,prefab中存放预置对象,scprits存放脚本,sprites用来存放贴图。

准备资源
获取FlappyBird的贴图资源和音效资源。把资源导入到sprites文件夹下,选中atlas,在Inspector中进行编辑,如下图:

设置为sprite,模式为Multiple,点击按钮Sprite Editor进行相应的图片分隔。在弹出的对话框中,可以使用自动切图方式,如下图:

接下来就可以进行编码了

设置场景

为了区分场景的层次(主要是用来决定图层的顺序,sorting Layer的功能)以及编码的需求,建立一些tag sorting Layer和Layer。先点击Unity编辑器右上方的Layers下拉菜单并选择"Edit Layers...",如下图:

并填写如下信息:


移动的道路和障碍

一格道路有两个障碍(如下图),场景中用两格道路来反复循环(当一格道路移出屏幕后就重新调整位置,等待下一次出现在屏幕上),达到不断移动的效果。

下面是道路移动的部分代码road.cs


  1. public class road : MonoBehaviour {   
  2.     ....  
  3.     // Update is called once per frame  
  4.     void Update () {  
  5.         Vector3 pos = trans.position;  
  6.         pos.x -= speed * Time.deltaTime;  
  7.         trans.position = pos;  
  8.         if(pos.x <= -1.6f - 3.35f*idx) {   //当道路移出屏幕后,从小调整其位置  
  9.             Vector3 pp = roads[idx%2].transform.position;  
  10.             pp.x += 3.35f;  
  11.             idx++;  
  12.             roads[idx%2].transform.position = pp;  
  13.             if(isBegin){  
  14.                 roads[idx%2].GetComponent<roadGen>().gen();  
  15.             }  
  16.         }  
  17.     }  

道路障碍的显示与否(在欢迎页面,路面不需要障碍),以及障碍的生成都在文件roadGen.cs中,我把上下柱子合并成一个对象,在生成障碍时,只要使其在一定范围内上下移动就可以了。代码片段如下:

  1. public class roadGen : MonoBehaviour {    
  2.     public GameObject[] zhuzi;    
  3.     public float down=3.8f, upper = 6.0f;     ...    
  4.     public void gen() { // 一格道路有两个柱子    
  5.         zhuzi[0].SetActive(true);    
  6.         zhuzi[1].SetActive(true);    
  7.         Vector3 p = zhuzi[0].transform.localPosition;      
  8.         float vv = Random.value;    
  9.         p.y = Mathf.Lerp(down, upper, vv);     
  10.         zhuzi[0].transform.localPosition = p; //设置第一个柱子的位置    
  11.     
  12.         p = zhuzi[1].transform.localPosition;    
  13.          vv = Random.value;    
  14.         p.y = Mathf.Lerp(down, upper, vv);    
  15.         zhuzi[1].transform.localPosition = p; //设置第二个柱子的位置  
  16.     }    
  17.     
  18.     public void hidden() {    
  19.         zhuzi[0].SetActive(false);    
  20.         zhuzi[1].SetActive(false);    
  21.     }    
  22. }
最后在障碍物和地面都添加BoxCollider2D,使其能够获取碰撞消息。

大嘴唇的小鸟

首先小鸟有个飞行的帧动画,在sprite文件夹下的atlas中,选择三个帧,直接拖动到场景中,unity自动形成了一个带有帧动画的sprite。选中该sprite,在Window/Animation界面中,调整sprite的播放时间,如下图:

同样要给小鸟一个BoxCollider2D的Component,使其能够响应碰撞,还要添加Rigibody2D。具体请参考例子。这里会涉及到两个脚本(时间匆忙,没怎么考虑设计):bird.cs和clider.cs;前者用来向小鸟施加力的作用,后者处理碰撞。
这个游戏的最主要部分就是 clider .cs,这个文件处理得分和是否碰撞到障碍物。代码如下:

  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class clider : MonoBehaviour {  
  5.     public score s;  
  6.     public int clideNum;  
  7.     public string tag;  
  8.     public bool isSuccess = false, isFail = false;  
  9.   
  10.     // Use this for initialization  
  11.     void Start () {  
  12.         s = GameObject.Find("score").GetComponent<score>();  
  13.         clideNum = 0;  
  14.         tag = "";  
  15.         isSuccess = false;  
  16.         isFail = false;  
  17.     }  
  18.       
  19.     // Update is called once per frame  
  20.     void Update () {  
  21.     }  
  22.     void OnTriggerEnter2D(Collider2D other) {  
  23.   
  24.         if(other.gameObject.tag.Equals("success")) {  
  25.             if(!isSuccess) {  
  26.                 print("===trigger enter==");  
  27.                 isSuccess = true;  
  28.                 s.success();  
  29.                 print ("success");  
  30.             }  
  31.         } else if(!isFail) {  
  32.             print("===trigger enter==");  
  33.             isFail = true;  
  34.             s.fail();  
  35.             print ("fail");  
  36.         }  
  37.     }  
  38.   
  39.     void OnTriggerExit2D(Collider2D other) {  
  40.         print("===trigger exit==");  
  41.         isSuccess = false;  
  42.     }  
  43.   
  44.     void OnCollisionEnter2D(Collision2D other) {   
  45.   
  46.         if(other.gameObject.tag.Equals("success")) {  
  47.             if(!isSuccess) {  
  48.                 print("===collision enter==");  
  49.                 isSuccess = true;  
  50.                 s.success();  
  51.             }  
  52.         } else if(!isFail) {  
  53.             print("===collision enter==");  
  54.             isFail = true;  
  55.             s.fail();  
  56.         }  
  57.     }  
  58.     void OnCollisionExit2D(Collision2D coll) {  
  59.         print("===collision exit==");  
  60.         isSuccess = false;  
  61.     }  
  62.   
  63.     public void reset() {  
  64.         isSuccess = false;  
  65.         isFail = false;  
  66.     }  
  67. }  

欢迎页面

欢迎页面有个小鸟的动画,并且能够响应触摸后开始游戏(在isReady.cs中实现)。
小鸟的动画就是上下摆动的过程,选择小鸟,然后在Animation界面中,添加Position属性,并调节如下图:

isReady.cs的代码如下:


  1. public class isReady : MonoBehaviour {    
  2.     public GameObject road, bird;    
  3.     // Use this for initialization    
  4.     void Start () {    
  5.         
  6.     }    
  7.         
  8.     // Update is called once per frame    
  9.     void Update () {    
  10.         if(Input.GetButtonDown("Fire1")){ //用户触摸屏幕之后,就开始游戏了    
  11.             gameObject.SetActive(false);    
  12.             road.GetComponent<road>().isBegin = true;    
  13.             bird.GetComponent<Rigidbody2D>().isKinematic = false;//欢迎页面,这里设置为true,使小鸟不响应重力,开始后要设置为false   
  14.             bird.GetComponent<Animator>().enabled = false;    
  15.         }    
  16.     }    
  17. }    

结算页面

结算页面开始使隐藏的,等用户输了之后,就会播放一个动画并显示,当用户点击play按钮后,游戏重置到欢迎页面。结算页面涉及到了脚本restart.cs
这里游戏重置的时,用到了BroadcastMessage的技术,即查找所有tag为needReset的对象,并调用其自身以及子对象中的代码中的reset函数来进行游戏的重置。代码如下:
  1. public class restart : MonoBehaviour {    
  2.     public Camera cam2d;    
  3.     public GameObject ready;     
  4.         
  5.     void Update () {    
  6.         if(Input.GetButtonDown("Fire1")){     
  7.             Collider2D h = Physics2D.OverlapPoint(cam2d.ScreenToWorldPoint(Input.mousePosition), (1<<LayerMask.NameToLayer("btn")));      
  8.             if(h) {  // 如果点击play按钮    
  9.                 gameObject.SetActive(false);    
  10.                 Time.timeScale = 1;     
  11.                 ready.SetActive(true);    
  12.                 GameObject[] resets = GameObject.FindGameObjectsWithTag("needReset"); //查找所有tag为needReset的对象   
  13.                 foreach(GameObject r in resets) {    
  14.                     r.BroadcastMessage("reset"); //调用其自身以及子对象中的代码中的reset函数来进行游戏的重置  
  15.                 }    
  16.             }      
  17.         }    
  18.     }