使用Unity製作俄羅斯方塊遊戲
阿新 • • 發佈:2019-01-10
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
- using UnityEngine;
- using System.Collections;
- public class Block : MonoBehaviour {
- //根據字串來定義方塊的形狀//
- public string[] block;
- private bool[,] blockMatrix;//方塊的矩陣/
- private float fallSpeed;//下降速度/
- private int halfSize;
- private float halfSizeFloat;
- private int xPosition;
- private int yPosition;
- private bool dropped=false;//是否下降/
- private int size;
- private float pressInterval=0;//按鍵時間間隔/
- void Start () {
- size=block.Length;
- int width=block[0].Length;
- //不符合條件的方塊彈出錯誤資訊//
- if(size<2){
- Debug.LogError("Block must have at lest two lines!");
- return;
- }
- if(width != size){
- Debug.LogError("Block width and height must be the same!");
- return;
- }
- if(size > Manager.user.maxBlockSize){
- Debug.LogError("Block must not be larger than "+Manager.user.maxBlockSize);
- return;
- }
- for(int i=1;i<size;i++){
- if(block[i].Length != block[i-1].Length){
- Debug.LogError("All line in the block must be the same height!");
- return;
- }
- }
- //halfSize為整型——用於標記陣列,halfSizeFloat為浮點型——用於定位螢幕上的位置
- halfSize = size/2;
- halfSizeFloat = size * 0.5f;
- //將字串陣列轉換成bool型別的陣列
- blockMatrix=new bool [size,size];
- for(int y=0;y<size;y++){
- for(int x=0;x<size;x++){
- if(block[y][x]=="1"[0]){
- blockMatrix[x,y]=true;
- //字串為1的地方建立一個cube//
- Transform t=Instantiate(Manager.user.cube,new Vector3(x-halfSizeFloat,(size-y)+halfSizeFloat-size,0.0f),Quaternion.identity) as Transform;
- //將其拖放到該Block下//
- t.parent=transform;
- }
- }
- }
- //初始化block的位置,如果方塊的大小為偶數,則+0,為奇數則+0.5f
- transform.position= new Vector3 (Manager.user.GetFieldWidth()/2+(size%2==0? 0.0f:0.5f),transform.position.y,transform.position.z);
- xPosition=(int)(transform.position.x-halfSizeFloat);
- yPosition=Manager.user.GetFieldHeight()-1;
- transform.position= new Vector3 (transform.position.x,yPosition-halfSizeFloat,transform.position.z);
- fallSpeed=Manager.user.blockNormalSpeed;
- //監測是否有重疊的方塊,如果存在,則遊戲結束!/
- if(Manager.user.CheckBlock(blockMatrix,xPosition,yPosition)){
- Manager.user.GameOver();
- return;
- }
- //監測輸入
- StartCoroutine(CheckInput());
- StartCoroutine(Delay((1.0f/Manager.user.blockNormalSpeed)*2.0f));
- StartCoroutine(Fall());
- }
- /// <summary>
- /// Delay the specified time.
- /// </summary>
- IEnumerator Delay(float time){
- float t=0.0f;
- while(t<time && !dropped){
- t += Time.deltaTime;
- yield return 0;
- }
- }
- /// <summary>
- /// 方塊降落
- /// </summary>
- IEnumerator Fall(){
- while(true){
- yPosition--;
- //監測方塊移動一行是否產生碰撞/
- if(Manager.user.CheckBlock(blockMatrix,xPosition,yPosition)){
- Manager.user.SetBlock(blockMatrix,xPosition,yPosition+1);
- Destroy(gameObject);
- break;
- }
- //方塊降落一個單位/
- for(float i=yPosition+1;i>yPosition;i-= Time.deltaTime*fallSpeed){
- transform.position=new Vector3 (transform.position.x,i-halfSizeFloat,transform.position.z);
- yield return 0;
- }
- }
- }
- /// <summary>
- /// Checks the input.
- /// </summary>
- IEnumerator CheckInput(){
- while(true){
- pressInterval += Time.deltaTime;
- if(Input.GetKey(KeyCode.LeftArrow) && pressInterval>0.1f){
- StartCoroutine(MoveHorizontal(-1));
- pressInterval=0;
- }else if(Input.GetKey(KeyCode.RightArrow) && pressInterval>0.1f){
- StartCoroutine(MoveHorizontal(1));
- pressInterval=0;
- }
- if(Input.GetKeyDown(KeyCode.UpArrow)){
- RotateBlock();
- }
- if(Input.GetKey(KeyCode.DownArrow)){
- fallSpeed=Manager.user.blockDropSpeed;
- dropped=true;
- break;
- }
- yield return 0;
- }
- }
- /// <summary>
- /// Moves the horizontal.
- /// </summary>
- IEnumerator MoveHorizontal(int dir){
- if(!Manager.user.CheckBlock(blockMatrix,xPosition+dir,yPosition)){
- transform.position += new Vector3 (dir,transform.position.y,transform.position.z);
- xPosition += dir;
- yield return new WaitForSeconds(Manager.user.blockMoveDelay);
- }
- }
- /// <summary>
- /// Rotates the block.
- /// 順時針旋轉90度,並將其結果儲存到tempMatrix中/
- /// </summary>
- void RotateBlock(){
- bool[,] tempMatrix=new bool [size,size];
- for(int y=0;y<size;y++){
- for(int x=0;x<size;x++){
- tempMatrix[y,x]=blockMatrix[x,(size-1)-y];
- }
- }
- //如果旋轉後的方塊沒有與已存在的方塊重疊,則將旋轉後的矩陣複製,並且顯示旋轉後的方塊/
- if(!Manager.user.CheckBlock(tempMatrix,xPosition,yPosition)){
- System.Array.Copy(tempMatrix,blockMatrix,size*size);
- transform.Rotate(Vector3.forward*(-90.0f));
- }
- }
- }
- using UnityEngine;
- using System.Collections;
- public class Manager : MonoBehaviour {
- public int FieldWidth = 18;//領域寬度/
- public int FieldHeight = 19;//領域高度/
- public int maxBlockSize = 4;//最大方塊尺寸/
- public float blockNormalSpeed=2.0f;//方塊正常下降速度//
- public int blockDropSpeed=30;//方塊下降速度/
- public float blockMoveDelay=0.1f;//方塊移動延遲/
- public int rowsClearedToSpeedup=10;//行清加速/
- public float speedupAmount=0.5f;//加速數量/
- public GameObject[] blocks;//塊//
- public Transform cube;//盒子//
- public Transform leftWall;//左邊牆壁
- public Transform rightWall;//右邊牆壁
- private int fieldWidth;
- private int fieldHeight;
- private bool[,] field;//場景區域的bool值/
- private Transform[] cubeReferences;
- private int[] cubePositions;
- private int rowsCleared =0;
- public static Manager user;
- void Start () {
- if(!user){
- user=this;
- }else{
- Debug.LogError("在這個指令碼中只允許存在一個例項!");
- return;
- }
- //場景區域寬度增加2*最大方塊尺寸(左右各一個)/
- fieldWidth = FieldWidth + maxBlockSize*2;
- //場景區域高度增加1*最大方塊尺寸(頂端)/
- fieldHeight = FieldHeight + maxBlockSize;
- field = new bool[fieldWidth,fieldHeight];
- //將牆壁放入到field陣列中,true=block,false=open
- //0=bottom,fieldHeight-1=top
- for(int i=0;i<fieldHeight;i++){
- for(int j=0;j<maxBlockSize;j++){
- field[j,i]=true;
- field[fieldWidth-1-j,i]=true;
- }
- }
- for(int i=0;i<fieldWidth;i++){
- field[i,0]=true;
- }
- //設定牆壁的位置//
- leftWall.position=new Vector3 (maxBlockSize-1,leftWall.position.y,leftWall.position.z);
- rightWall.position=new Vector3 (fieldWidth-maxBlockSize,rightWall.position.y,rightWall.position.z);
- Camera.main.transform.position=new Vector3 (fieldWidth/2,fieldHeight/2-1,-10);
- cubeReferences=new Transform [fieldWidth*fieldHeight];
- cubePositions=new int [fieldWidth*fieldHeight];
- //例項化一個方塊//
- SpawnBlock();
- }
- //例項化方塊//
- void SpawnBlock(){
- Instantiate(blocks[Random.Range(0,blocks.Length)]);
- }
- public int GetFieldWidth(){
- return fieldWidth;
- }
- public int GetFieldHeight(){
- return fieldHeight;
- }
- /// <summary>
- /// 監測blockMatrix是否已經存在block
- /// 我們從bottom向top監測
- /// </summary>
- public bool CheckBlock(bool[,] blockMatrix,int xPos,int yPos){
- //GetLength(0)獲取第一維元素的長度//
- int size=blockMatrix.GetLength(0);
- //監測順序為bottom-top,left-right
- for(int y=size-1;y>=0;y--){
- for(int x=0;x<size;x++){
- if(blockMatrix[x,y] && field[xPos+x,yPos-y]){
- return true;
- }
- }
- }
- return false;
- }
- /// <summary>
- /// 監測螢幕上停止的方塊
- /// 僅僅使用孩子物體是不行的,因為孩子方塊的方向是不同的
- /// 使用Y軸會使他們位置混亂的,所以我們要使用一致的CollapseRow
- /// 在領域中,我們將blockMatrix寫入到相應的位置
- /// </summary>
- public void SetBlock(bool[,] blockMatrix, int xPos,int yPos){
- int size=blockMatrix.GetLength(0);
- for(int y=0;y<size;y++){
- for(int x=0;x<size;x++){
- if(blockMatrix[x,y]){
- Instantiate(cube,new Vector3(xPos+x,yPos-y,0.0f),Quaternion.identity);
- field[xPos+x,yPos-y]=true;
- }
- }
- }
- StartCoroutine(CheckRows(yPos-size,size));
- SpawnBlock();
- }
- /// <summary>
- /// 監測領域中的每一行/
- /// </summary>
- public IEnumerator CheckRows(int yStart,int size){
- //等待一幀//
- yield return 0;
- if(yStart<1)
- yStart=1;//確保從bottom開始//
- for(int y=yStart;y<yStart+size;y++){
- int x=0;
- for(x=maxBlockSize;x<fieldWidth-maxBlockSize;x++){
- //不需要監測左右牆壁//
- if(!field[x,y])
- break;
- }
- //當該迴圈結束後,x=fieldWidth-maxBlockSize,這就表示該行已被填滿了!!!!//
- if(x==fieldWidth-maxBlockSize){
- StartCoroutine(CollapseRows(y));
- y--;
- }
- }
- }
- /// <summary>
- /// 消除一行
- /// </summary>
- public IEnumerator CollapseRows(int yStart){
- //將陣列中的行下移,最有效的就是刪除當前行(yStart)/
- for(int y=yStart;y<fieldHeight-1;y++){
- for(int x=maxBlockSize;x<fieldWidth-maxBlockSize;x++){
- field[x,y]=field[x,y+1];
- }
- }
- //確保top層被清空/
- for(int x=maxBlockSize;x<fieldWidth-maxBlockSize;x++){
- field[x,fieldHeight-1]=false;
- }
- //消除該行的cube,儲存該行上面的cube//
- GameObject[] cubes=GameObject.FindGameObjectsWithTag("cube");
- int cubeToMove=0;
- foreach( GameObject c in cubes){
- if((int)(c.transform.position.y)>yStart){
- cubePositions[cubeToMove]=(int)c.transform.position.y;
- cubeReferences[cubeToMove++]=c.transform;
- }else if((int)(c.transform.position.y)==yStart){
- //銷燬/
- Destroy(c);
- }
- }
- //將靠近的方塊下一個立方/
- //Mathf.Lerp的第三個引數固定在1.0,這樣使transform.position.y更加的精確。/
- float t=0.0f;
- while(t<=1.0f){
- t += Time.deltaTime*5.0f;
- for(int i=0;i<cubeToMove;i++){
- cubeReferences[i].position=new Vector3 (cubeReferences[i].position.x,Mathf.Lerp(cubePositions[i],cubePositions[i]-1,t),cubeReferences[i].position.z);
- }
- yield return 0;
- }
- //當行被清空的時候,讓方塊下降速度加快/
- if(++rowsCleared==rowsClearedToSpeedup){
- blockNormalSpeed+=speedupAmount;
- rowsCleared=0;
- }
- }
- /// <summary>
- /// Games the over.
- /// </summary>
- public void GameOver(){
- Debug.Log("Game Over!");
- }
- /// <summary>
- /// Prints the field.用於Debug
- /// </summary>
- void PrintField(){
- string fieldChars="";
- for(int y=fieldHeight-1;y>=0;y--){
- for(int x=0;x<fieldWidth;x++){
- fieldChars += field[y,x]?"1":"0";
- }
- fieldChars +="\n";
- }
- Debug.Log(fieldChars);
- }
- }