1. 程式人生 > >使用Unity製作俄羅斯方塊遊戲

使用Unity製作俄羅斯方塊遊戲

1. 操作環境

   Unity3D 4.1.0版本、Win 7

   備註:該方法並非本人原創,我也是根據別人的程式碼來學習的。

2. 思路分析

   該方法中,只有2個指令碼,一個是控制方塊的(Block.cs),另外一個是控制遊戲場景的(Manager.cs)。遊戲完成後效果如下圖:

   2.1 方塊的構造(Block.cs)

   俄羅斯方塊一共有7種不同的方塊。每個種類有4個小方塊組成的。我們使用一個string[ ]陣列來記錄方塊的形狀。

   從上圖我們可以看出,1表示有方塊,0表示沒有方塊。我們就可以根據該陣列來例項化了,而且該陣列的長度和寬度是相同的。這樣方便我們對他進行旋轉、移動等操作。

   2.2 遊戲場景(Manager.cs)

    我們通過自己的喜歡來設計場景,但是要保證寬度必須是小方塊的整數倍。不然就會產生無法填滿的結果了。在這裡我設計了領域寬度為18單位 (FieldWidth = 18),領域高度為19單位(FieldHeight =  19)。我們定義個bool值陣列來表示領域中的狀態,0表示該處為空(即無方塊),1表示有方塊存在。我們對方塊的操作都需要來監測場景中的狀態,當滿 足其狀態要求時,就可以執行相應的操作了。

3. 程式碼

   Block.CS

  1. using UnityEngine;
  2. using System.Collections;
  3. public class Block : MonoBehaviour {
  4.     //根據字串來定義方塊的形狀//
  5. public string[] block;
  6.     private bool[,] blockMatrix;//方塊的矩陣/
  7. private float fallSpeed;//下降速度/
  8. private int halfSize;
  9.     private float halfSizeFloat;
  10.     private int xPosition;
  11.     private int yPosition;
  12.     private bool dropped=false;//是否下降/
  13. private int size;
  14.     private float pressInterval=0;//按鍵時間間隔/
  15. void Start () {
  16.         size=block.Length;
  17.         int width=block[0].Length;    
  18.         //不符合條件的方塊彈出錯誤資訊//
  19. if(size<2){
  20.             Debug.LogError("Block must have at lest two lines!");
  21.             return;
  22.         }
  23.         if(width != size){
  24.             Debug.LogError("Block width and height must be the same!");
  25.             return;
  26.         }
  27.         if(size > Manager.user.maxBlockSize){
  28.             Debug.LogError("Block must not be larger than "+Manager.user.maxBlockSize);
  29.             return;
  30.         }
  31.         for(int i=1;i<size;i++){
  32.             if(block[i].Length != block[i-1].Length){
  33.                 Debug.LogError("All line in the block must be the same height!");
  34.                 return;
  35.             }
  36.         }
  37.         //halfSize為整型——用於標記陣列,halfSizeFloat為浮點型——用於定位螢幕上的位置
  38. halfSize = size/2;
  39.         halfSizeFloat = size * 0.5f;
  40.         //將字串陣列轉換成bool型別的陣列
  41. blockMatrix=new bool [size,size];
  42.         for(int y=0;y<size;y++){
  43.             for(int x=0;x<size;x++){
  44.                 if(block[y][x]=="1"[0]){
  45.                     blockMatrix[x,y]=true;
  46.                     //字串為1的地方建立一個cube//
  47.                     Transform t=Instantiate(Manager.user.cube,new Vector3(x-halfSizeFloat,(size-y)+halfSizeFloat-size,0.0f),Quaternion.identity) as Transform;
  48.                     //將其拖放到該Block下//
  49. t.parent=transform;
  50.                 }
  51.             }
  52.         }
  53.         //初始化block的位置,如果方塊的大小為偶數,則+0,為奇數則+0.5f
  54.         transform.position= new Vector3 (Manager.user.GetFieldWidth()/2+(size%2==0? 0.0f:0.5f),transform.position.y,transform.position.z);
  55.         xPosition=(int)(transform.position.x-halfSizeFloat);
  56.         yPosition=Manager.user.GetFieldHeight()-1;
  57.         transform.position= new Vector3 (transform.position.x,yPosition-halfSizeFloat,transform.position.z);
  58.         fallSpeed=Manager.user.blockNormalSpeed;
  59.         //監測是否有重疊的方塊,如果存在,則遊戲結束!/
  60. if(Manager.user.CheckBlock(blockMatrix,xPosition,yPosition)){
  61.             Manager.user.GameOver();
  62.             return;
  63.         }
  64.         //監測輸入
  65. StartCoroutine(CheckInput());
  66.         StartCoroutine(Delay((1.0f/Manager.user.blockNormalSpeed)*2.0f));
  67.         StartCoroutine(Fall());
  68.     }
  69.     /// <summary>
  70.     /// Delay the specified time.
  71.     /// </summary>  
  72.     IEnumerator Delay(float time){
  73.         float t=0.0f;
  74.         while(t<time &amp;&amp; !dropped){
  75.             t += Time.deltaTime;
  76.             yield return 0;
  77.         }
  78.     }
  79.     /// <summary>
  80.     /// 方塊降落
  81. /// </summary>
  82.     IEnumerator Fall(){
  83.         while(true){
  84.             yPosition--;
  85.             //監測方塊移動一行是否產生碰撞/
  86. if(Manager.user.CheckBlock(blockMatrix,xPosition,yPosition)){
  87.                 Manager.user.SetBlock(blockMatrix,xPosition,yPosition+1);
  88.                 Destroy(gameObject);
  89.                 break;
  90.             }
  91.             //方塊降落一個單位/
  92. for(float i=yPosition+1;i>yPosition;i-= Time.deltaTime*fallSpeed){
  93.                 transform.position=new Vector3 (transform.position.x,i-halfSizeFloat,transform.position.z);
  94.                 yield return 0;
  95.             }
  96.         }
  97.     }
  98.     /// <summary>
  99.     /// Checks the input.
  100.     /// </summary>
  101.     IEnumerator CheckInput(){
  102.         while(true){
  103.             pressInterval += Time.deltaTime;
  104.             if(Input.GetKey(KeyCode.LeftArrow) &amp;&amp; pressInterval>0.1f){
  105.                 StartCoroutine(MoveHorizontal(-1));
  106.                 pressInterval=0;
  107.             }else if(Input.GetKey(KeyCode.RightArrow) &amp;&amp; pressInterval>0.1f){
  108.                 StartCoroutine(MoveHorizontal(1));
  109.                 pressInterval=0;
  110.             }
  111.             if(Input.GetKeyDown(KeyCode.UpArrow)){
  112.                 RotateBlock();
  113.             }
  114.             if(Input.GetKey(KeyCode.DownArrow)){
  115.                 fallSpeed=Manager.user.blockDropSpeed;
  116.                 dropped=true;
  117.                 break;
  118.             }
  119.             yield return 0;
  120.         }
  121.     }
  122.     /// <summary>
  123.     /// Moves the horizontal.
  124.     /// </summary>  
  125.     IEnumerator MoveHorizontal(int dir){
  126.         if(!Manager.user.CheckBlock(blockMatrix,xPosition+dir,yPosition)){
  127.             transform.position += new Vector3 (dir,transform.position.y,transform.position.z);
  128.             xPosition += dir;
  129.             yield return new WaitForSeconds(Manager.user.blockMoveDelay);
  130.         }
  131.     }
  132.     /// <summary>
  133.     /// Rotates the block.
  134.     /// 順時針旋轉90度,並將其結果儲存到tempMatrix中/
  135.     /// </summary>
  136.     void RotateBlock(){
  137.         bool[,] tempMatrix=new bool [size,size];
  138.         for(int y=0;y<size;y++){
  139.             for(int x=0;x<size;x++){
  140.                 tempMatrix[y,x]=blockMatrix[x,(size-1)-y];
  141.             }
  142.         }     
  143.         //如果旋轉後的方塊沒有與已存在的方塊重疊,則將旋轉後的矩陣複製,並且顯示旋轉後的方塊/
  144. if(!Manager.user.CheckBlock(tempMatrix,xPosition,yPosition)){
  145.             System.Array.Copy(tempMatrix,blockMatrix,size*size);
  146.             transform.Rotate(Vector3.forward*(-90.0f));
  147.         }
  148.     }
  149. }
複製程式碼 Manager.cs
  1. using UnityEngine;
  2. using System.Collections;
  3. public class Manager : MonoBehaviour {      
  4.     public int FieldWidth = 18;//領域寬度/
  5. public int FieldHeight = 19;//領域高度/
  6. public int maxBlockSize = 4;//最大方塊尺寸/
  7. public float blockNormalSpeed=2.0f;//方塊正常下降速度//
  8. public int blockDropSpeed=30;//方塊下降速度/
  9. public float blockMoveDelay=0.1f;//方塊移動延遲/
  10. public int rowsClearedToSpeedup=10;//行清加速/
  11. public float speedupAmount=0.5f;//加速數量/
  12. public GameObject[] blocks;//塊//
  13. public Transform cube;//盒子//  
  14. public Transform leftWall;//左邊牆壁
  15. public Transform rightWall;//右邊牆壁
  16. private int fieldWidth;
  17.     private int fieldHeight;
  18.     private bool[,] field;//場景區域的bool值/
  19. private Transform[] cubeReferences;
  20.     private  int[] cubePositions;
  21.     private int rowsCleared =0;
  22.     public static Manager user;
  23.     void Start () {
  24.         if(!user){
  25.             user=this;
  26.         }else{
  27.             Debug.LogError("在這個指令碼中只允許存在一個例項!");
  28. return;
  29.         }
  30.         //場景區域寬度增加2*最大方塊尺寸(左右各一個)/
  31. fieldWidth = FieldWidth + maxBlockSize*2;
  32.         //場景區域高度增加1*最大方塊尺寸(頂端)/
  33. fieldHeight = FieldHeight + maxBlockSize;
  34.         field = new bool[fieldWidth,fieldHeight];
  35.         //將牆壁放入到field陣列中,true=block,false=open
  36.         //0=bottom,fieldHeight-1=top
  37.         for(int i=0;i<fieldHeight;i++){
  38.             for(int j=0;j<maxBlockSize;j++){
  39.                 field[j,i]=true;
  40.                 field[fieldWidth-1-j,i]=true;
  41.             }
  42.         }
  43.         for(int i=0;i<fieldWidth;i++){ 
  44.             field[i,0]=true;
  45.         }
  46.         //設定牆壁的位置//
  47. leftWall.position=new Vector3 (maxBlockSize-1,leftWall.position.y,leftWall.position.z);
  48.         rightWall.position=new Vector3 (fieldWidth-maxBlockSize,rightWall.position.y,rightWall.position.z);
  49.         Camera.main.transform.position=new Vector3 (fieldWidth/2,fieldHeight/2-1,-10);
  50.         cubeReferences=new Transform [fieldWidth*fieldHeight];
  51.         cubePositions=new int [fieldWidth*fieldHeight];
  52.         //例項化一個方塊//
  53. SpawnBlock();
  54.     } 
  55.     //例項化方塊//
  56. void SpawnBlock(){
  57.         Instantiate(blocks[Random.Range(0,blocks.Length)]);
  58.     }
  59.     public int GetFieldWidth(){
  60.         return fieldWidth;
  61.     }
  62.     public int GetFieldHeight(){
  63.         return fieldHeight;
  64.     }
  65.     /// <summary>
  66.     /// 監測blockMatrix是否已經存在block
  67.     /// 我們從bottom向top監測
  68. /// </summary>  
  69.     public bool CheckBlock(bool[,] blockMatrix,int xPos,int yPos){
  70.         //GetLength(0)獲取第一維元素的長度//
  71. int size=blockMatrix.GetLength(0);
  72.         //監測順序為bottom-top,left-right
  73.         for(int y=size-1;y>=0;y--){
  74. for(int x=0;x<size;x++){
  75.                 if(blockMatrix[x,y] &amp;&amp; field[xPos+x,yPos-y]){
  76.                     return true;
  77.                 }
  78.             }
  79.         }
  80.         return false;
  81.     }
  82.     /// <summary>
  83.     /// 監測螢幕上停止的方塊
  84. /// 僅僅使用孩子物體是不行的,因為孩子方塊的方向是不同的
  85. /// 使用Y軸會使他們位置混亂的,所以我們要使用一致的CollapseRow
  86.     /// 在領域中,我們將blockMatrix寫入到相應的位置
  87. /// </summary>  
  88.     public void SetBlock(bool[,] blockMatrix, int xPos,int yPos){
  89.         int size=blockMatrix.GetLength(0);
  90.         for(int y=0;y<size;y++){
  91.             for(int x=0;x<size;x++){
  92.                 if(blockMatrix[x,y]){
  93.                     Instantiate(cube,new Vector3(xPos+x,yPos-y,0.0f),Quaternion.identity);
  94.                     field[xPos+x,yPos-y]=true;
  95.                 }
  96.             }
  97.         }
  98.         StartCoroutine(CheckRows(yPos-size,size));
  99.         SpawnBlock();
  100.     }
  101.     /// <summary>
  102.     /// 監測領域中的每一行/
  103.     /// </summary>  
  104.     public IEnumerator CheckRows(int yStart,int size){
  105.         //等待一幀//
  106. yield return 0;
  107.         if(yStart<1)
  108.             yStart=1;//確保從bottom開始//
  109. for(int y=yStart;y<yStart+size;y++){
  110.             int x=0;
  111.             for(x=maxBlockSize;x<fieldWidth-maxBlockSize;x++){
  112.                 //不需要監測左右牆壁//
  113. if(!field[x,y])
  114.                     break;
  115.             }
  116.             //當該迴圈結束後,x=fieldWidth-maxBlockSize,這就表示該行已被填滿了!!!!//
  117. if(x==fieldWidth-maxBlockSize){
  118.                 StartCoroutine(CollapseRows(y));
  119.                 y--;
  120.             }
  121.         }
  122.     }
  123.     /// <summary>
  124.     /// 消除一行
  125. /// </summary>  
  126.     public IEnumerator CollapseRows(int yStart){
  127.         //將陣列中的行下移,最有效的就是刪除當前行(yStart)/
  128. for(int y=yStart;y<fieldHeight-1;y++){
  129.             for(int x=maxBlockSize;x<fieldWidth-maxBlockSize;x++){
  130.                 field[x,y]=field[x,y+1];
  131.             }
  132.         }
  133.         //確保top層被清空/
  134. for(int x=maxBlockSize;x<fieldWidth-maxBlockSize;x++){
  135.             field[x,fieldHeight-1]=false;
  136.         }
  137.         //消除該行的cube,儲存該行上面的cube//
  138.         GameObject[] cubes=GameObject.FindGameObjectsWithTag("cube");
  139.         int cubeToMove=0;
  140.         foreach( GameObject c in cubes){
  141.             if((int)(c.transform.position.y)>yStart){
  142.                 cubePositions[cubeToMove]=(int)c.transform.position.y;
  143.                 cubeReferences[cubeToMove++]=c.transform;
  144.             }else if((int)(c.transform.position.y)==yStart){
  145.                 //銷燬/
  146. Destroy(c);
  147.             }
  148.         }
  149.         //將靠近的方塊下一個立方/
  150.         //Mathf.Lerp的第三個引數固定在1.0,這樣使transform.position.y更加的精確。/
  151. float t=0.0f;
  152.         while(t<=1.0f){
  153.             t += Time.deltaTime*5.0f;
  154.             for(int i=0;i<cubeToMove;i++){
  155.                 cubeReferences[i].position=new Vector3 (cubeReferences[i].position.x,Mathf.Lerp(cubePositions[i],cubePositions[i]-1,t),cubeReferences[i].position.z);
  156.             }
  157.             yield return 0;
  158.         }
  159.         //當行被清空的時候,讓方塊下降速度加快/
  160. if(++rowsCleared==rowsClearedToSpeedup){
  161.             blockNormalSpeed+=speedupAmount;
  162.             rowsCleared=0;
  163.         }
  164.     }
  165.     /// <summary>
  166.     /// Games the over.
  167.     /// </summary>
  168.     public void GameOver(){
  169.         Debug.Log("Game Over!");
  170.     }
  171.     /// <summary>
  172.     /// Prints the field.用於Debug
  173.     /// </summary>
  174.     void PrintField(){
  175.         string fieldChars="";
  176.         for(int y=fieldHeight-1;y>=0;y--){
  177. for(int x=0;x<fieldWidth;x++){
  178.                 fieldChars += field[y,x]?"1":"0";
  179.             }
  180.             fieldChars +="\n";
  181.         }
  182.         Debug.Log(fieldChars);
  183.     }
  184. }