アマゾンバナーリンク

Unityで作るテトリス

2020年6月4日

こんにちは!ジェイです。パズルゲームの王道といえばテトリス!!ということで今回はテトリスの作り方を説明します。

セルを判定するクラス

1つのセルについて、判定や結合するためのクラスを用意します。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CCell
{
	const int MAX_X = 16;
	const int MAX_Y = 14;
	char[] Cell;
	bool[] CellFlags;
	int XSize, YSize;
	public CCell()
	{
		XSize = MAX_X;
		YSize = MAX_Y;
		Cell = new char[XSize * YSize];
		CellFlags = new bool[XSize * YSize];
	}
	public CCell(int xsize, int ysize)
	{
		XSize = xsize;
		YSize = ysize;
		Cell = new char[XSize * YSize];
		CellFlags = new bool[XSize * YSize];
	}
	public CCell(string cell, int size)
	{
		int x = 0;
		int y = 0;
		for (int i = 0; i < size; i++)
		{
			switch (cell[i])
			{
				case '\n':
					y++;
					x++;
					if (x > XSize) XSize = x;
					if (y > YSize) YSize = y;
					x = 0;
					break;
				default:
					x++;
					break;
			}
		}
		Cell = new char[XSize * YSize];
		CellFlags = new bool[XSize * YSize];
		for (int i = 0; i < XSize * YSize; i++)
		{
			Cell[i] = cell[i];
			CellFlags[i] = false;
		}
	}
	//~CCell() { delete[] Cell; }
	public void Init(string cell) 
	{
		Cell = new char[XSize * YSize];
		CellFlags = new bool[XSize * YSize];
		for (int i = 0; i < XSize * YSize; i++)
		{
			Cell[i] = cell[i];
			CellFlags[i] = false;
		}
	}
	public char Get(int x, int y) 
	{
		if (0 <= x && x < XSize && 0 <= y && y < YSize)
		{
			return Cell[x + y * XSize];
		}
		else
		{
			return ' ';
		}
	}
	public bool GetFlag(int x, int y)
	{
		if (0 <= x && x < XSize && 0 <= y && y < YSize)
		{
			return CellFlags[x + y * XSize];
		}
		else
		{
			return false;
		}
	}
	public void Set(int x, int y, char value) 
	{
		if (0 <= x && x < XSize && 0 <= y && y < YSize)
		{
			Cell[x + y * XSize] = value;
		}
	}
	public void Set(int x, int y, char value, bool flag)
	{
		if (0 <= x && x < XSize && 0 <= y && y < YSize)
		{
			Cell[x + y * XSize] = value;
			CellFlags[x + y * XSize] = flag;
		}
	}
	public void Set(int x, int y, CCell cell) 
	{
		for (int i = 0; i < cell.GetYSize(); i++)
	{
			for (int j = 0, jn = cell.GetXSize(); j < jn; j++)
			{
				Set(x + j, y + i, cell.Get(j, i));
			}
		}
	}
	private void Set(int x, int y, int v)
	{
		throw new NotImplementedException();
	}
	public void And(int value) 
	{
		int xs = GetXSize(), ys = GetYSize();
		for (int y = 0; y < ys; y++)
		{
			for (int x = 0; x < xs; x++)
			{
				Set(x, y, Get(x, y) & value);
			}
		}
	}
	public void Or(char value) 
	{
		int xs = GetXSize(), ys = GetYSize();
		for (int y = 0; y < ys; y++)
		{
			for (int x = 0; x < xs; x++)
			{
				Set(x, y, Get(x, y) | value);
			}
		}
	}
	public void Shift(int vx, int vy) 
	{
		int xs = GetXSize(), ys = GetYSize();
		int fx, tx, dx, fy, ty, dy;
		if (vx < 0)
		{
			fx = 0;
			tx = xs - 1;
			dx = -1;
		}
		else
		{
			fx = xs - 1;
			tx = 0;
			dx = 1;
		}
		if (vy < 0)
		{
			fy = 0;
			ty = ys - 1;
			dy = -1;
		}
		else
		{
			fy = ys - 1;
			ty = 0;
			dy = 1;
		}
		for (int y = fx; y != ty; y += dy)
		{
			for (int x = fx; x != tx; x += dx)
			{
				Set(x, y, Get(x - vx, y - vx));
			}
		}
	}
	public void Replace(char c0, char c1)
	{
		int xs = GetXSize(), ys = GetYSize();
		for (int y = 0; y < ys; y++)
		{
			for (int x = 0; x < xs; x++)
			{
				if (Get(x, y) == c0) Set(x, y, c1);
			}
		}
	}
	public void Swap(int xa, int ya, int xb, int yb) 
	{
		char c = Get(xa, ya);
		char d = Get(xb, yb);
		Set(xa, ya, d);
		Set(xb, yb, c);
	}
	public void Merge(int x, int y, CCell cell)
	{
		for (int i = 0; i < cell.GetYSize(); i++ )
	{
			for (int j = 0; j < cell.GetXSize(); j++)
			{
				if (cell.Get(j, i) != ' ')
				{
					Set(x + j, y + i, cell.Get(j, i));
				}
			}
		}
	}
	public bool Hit(int x, int y, CCell cell)
	{
		for (int i = 0; i < cell.GetYSize(); i++ )
		{
			for (int j = 0; j < cell.GetXSize(); j++)
			{
				if (cell.Get(j, i) != ' ' && Get(x + j, y + i) != ' ')
				{
					return true;
				}
			}
		}
		return false;
	}
	public void Clear(char value)
	{
		for (int i = 0; i < GetYSize(); i++ )
		{
			for (int j = 0, jn = GetXSize(); j < jn; j++)
			{
				Set(j, i, value);
			}
		}
	}
	public int GetXSize() { return XSize; }
	public int GetYSize() { return YSize; }
}

テトリスの本処理

上記のクラスはセル1つ分なので、それらの機能を使って実装します。以下に必要な処理をまとめてみました。

  • テトリスブロックの形を保存するセル
  • 地形を保存するセル
  • レバー入力か、落下タイマーが一定に達したら、ブロックを下げる
  • 移動処理
  • 落下判定処理
  • 横一列揃ったらセルを消す
  • 消した後にそのラインより上のブロックを下に落とす
  • ブロックを再出現させる
ブロックの種類

・結合したあとのブロックは#
・消す予定のブロックは+
・最初からある地形のブロックは=

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;

public static class CharExt
{
    public static int ToInt(this char self)
    {
        return self - '0';
    }
}

public class CTetorisu : MonoBehaviour
{

    CCell StageCell = new CCell();
    CCell[] BlockCell = new CCell[DROPPING_BLOCK_PATTERN_COUNT];
    CCell[] CurrentBlock = new CCell[DROPPING_BLOCK_CELL_SIZE];
    CCell[] NextBlock = new CCell[DROPPING_BLOCK_PATTERN_COUNT];
    public float X, Y;
    int CX, CY, VX, VY;
    public int State; 
    int Time, DropTime, Turn;
    bool PrevDown;
	const int DROPPING_BLOCK_CELL_SIZE = 4;
	const int DROPPING_BLOCK_PATTERN_COUNT = 28;
	const int DROPPING_BLOCK_TURN_COUNT = 4;
	const int DROPPING_BLOCK_FIELD_LEFT = 1;
	const int DROPPING_BLOCK_FIELD_RIGHT = 11;
	const int DROPPING_BLOCK_FIELD_TOP = 1;
	const int DROPPING_BLOCK_FIELD_BOTTOM = 12;
	const int DROPPING_BLOCK_NEXT_LEFT = 12;
	// ブロックのパターン
	string[,] DroppingBlockPattern = new string[DROPPING_BLOCK_PATTERN_COUNT,DROPPING_BLOCK_TURN_COUNT]
	{
        {
            "    ",
            " ## ",
            " ## ",
            "    ",
        },
        {
            "    ",
            " ## ",
            " ## ",
            "    ",
        },
        {
            "    ",
            " ## ",
            " ## ",
            "    ",
        },
        {
            "    ",
            " ## ",
            " ## ",
            "    ",
        },

        {
            "    ",
            "####",
            "    ",
            "    ",
        },
        {
            " #  ",
            " #  ",
            " #  ",
            " #  ",
        },
        {
            "    ",
            "####",
            "    ",
            "    ",
        },
        {
            " #  ",
            " #  ",
            " #  ",
            " #  ",
        },

        {
            "    ",
            "##  ",
            " ## ",
            "    ",
        },
        {
            "  # ",
            " ## ",
            " #  ",
            "    ",
        },
        {          
            "    ",
            "##  ",
            " ## ",
            "    ",
        },
        {
            "  # ",
            " ## ",
            " #  ",
            "    ",
        },

        {
            " #  ",
            " ## ",
            "  # ",
            "    ",
        },
        {
            "    ",
            " ## ",
            "##  ",
            "    ",
        },
        {
            " #  ",
            " ## ",
            "  # ",
            "    ",
        },
        {
            "    ",
            " ## ",
            "##  ",
            "    ",
        },


        {
            " #  ",
            " #  ",
            " ## ",
            "    ",
        },
        {
            "    ",
            "### ",
            "#   ",
            "    ",
        },
        {
            "##  ",
            " #  ",
            " #  ",
            "    ",
        },
        {
            "#   ",
            "### ",
            "    ",
            "    ",
        },

        {
            " ## ",
            " #  ",
            " #  ",
            "    ",
        },
        {
            "    ",
            "### ",
            "  # ",
            "    ",
        },
        {
            " #  ",
            " #  ",
            "##  ",
            "    ",
        },
        {
            "    ",
            "### ",
            "#   ",
            "    ",
        },

        {
            " #  ",
            "### ",
            "    ",
            "    ",
        },
        {
            " #  ",
            " ## ",
            " #  ",
            "    ",
        },
        {
            "    ",
            "### ",
            " #  ",
            "    ",
        },
        {
            " #  ",
            "##  ",
            " #  ",
            "    ",
        }
    };
    GameObject emptyGameObject;
    void Init()
    {
        int rand = UnityEngine.Random.Range(0, 7);
        for(int i = 0; i < DROPPING_BLOCK_TURN_COUNT; ++i)
        {
            Debug.Log(rand*4 + i);
            CurrentBlock[i] = NextBlock[rand*4+i];
        }
        
        NextBlock = BlockCell;//.OrderBy(i => Guid.NewGuid()).ToArray();
        CX = (DROPPING_BLOCK_FIELD_LEFT + DROPPING_BLOCK_FIELD_RIGHT - DROPPING_BLOCK_CELL_SIZE) / 2;
        CY = DROPPING_BLOCK_FIELD_TOP;
        X = CX;
        Y = CY;
        State = 0;
        DropTime = 0;
        Turn = 0;
        if (StageCell.Hit(CX, CY, CurrentBlock[Turn]))
        {
            State = 99999;
        }
        PrevDown = true;
        emptyGameObject = new GameObject("Empty Game Object");
        
        for (int y = 0; y < CurrentBlock[Turn].GetYSize(); y++)
        {
            for (int x = 0; x < CurrentBlock[Turn].GetXSize(); x++)
            {
                if (CurrentBlock[Turn].Get(x, y) == '#')
                {
                    GameObject go = Instantiate(Cube2, new Vector3(x, CurrentBlock[Turn].GetYSize() - y, 0), Quaternion.identity);
                    go.transform.parent = emptyGameObject.transform;
                }
            }
        }
        emptyGameObject.transform.position = new Vector3(X, (DROPPING_BLOCK_FIELD_BOTTOM - Y-1), 0);
    }
    public GameObject Cube,Cube2;
    public Material CubeMat;
    private void Start()
    {
        string temp = string.Empty;
        for (int i = 0; i < DROPPING_BLOCK_PATTERN_COUNT; i++)
        {
            temp = string.Empty;
            BlockCell[i] = new CCell(DROPPING_BLOCK_CELL_SIZE, DROPPING_BLOCK_CELL_SIZE);
            for (int j = 0; j < DROPPING_BLOCK_TURN_COUNT; j++)
            {
                temp += DroppingBlockPattern[i, j];
            }
            BlockCell[i].Init(temp);
        }
        NextBlock = BlockCell;
        string[] str =
        {
            "                \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "================\n",
        };
        temp = string.Empty;
        for (int i = 0; i < str.Length; ++i)
        {
            temp += str[i];
        }
        StageCell = new CCell(temp, temp.Length);
        for (int y = 0; y < StageCell.GetYSize(); y++)
        {
            for (int x = 0; x < StageCell.GetXSize(); x++)
            {
                if(StageCell.Get(x,y) == '=')
                {
                    Instantiate(Cube, new Vector3(x, StageCell.GetYSize()-y, 0), Quaternion.identity);
                }         
            }
        }
        Init();
    }

    private void Update()
    {
        if (State == 0)
        {
            DropTime++;

            // レバーを下に入力するか、落下タイマーが一定値に達したら、ブロックを下げる
            if ((Input.GetKey(KeyCode.DownArrow) && !PrevDown) || DropTime == 60)
            {
                // セル座標を更新したときに、ブロックがステージの壁や他のブロックに
                // 当たっているか調べる
                if (StageCell.Hit(CX, CY + 1, CurrentBlock[Turn]))
                {
                    GameObject[] objs = GameObject.FindGameObjectsWithTag("MoveCube");
                    for(int i = 0; i < objs.Length; ++i)
                    {
                        objs[i].tag = "Finish";
                        objs[i].GetComponent<MeshRenderer>().material = CubeMat;
                    }
                    // 接触する場合には、ブロックのセルをステージのセルに合流する
                    StageCell.Merge(CX, CY, CurrentBlock[Turn]);

                    // 消去判定状態へ移行する
                    // 着地後のブロックを落としたいならStateを2の落下判定状態にする
                    State = 4;
                }
                else
                {
                    // 接触しない場合には、落下タイマーとセル座標を更新する
                    DropTime = 0;
                    CY++;
                    VX = 0;
                    VY = 1;
                    Time = 0;
                    State = 1;
                }
            }// ブロックを左へ移動させる
            else if (Input.GetKey(KeyCode.LeftArrow)
                && !StageCell.Hit(CX - 1, CY, CurrentBlock[Turn]))
            {
                CX--;
                VX = -1;
                VY = 0;
                Time = 0;
                State = 1;
            } //ブロックを右へ移動させる
            else if (Input.GetKey(KeyCode.RightArrow) && !StageCell.Hit(CX + 1, CY, CurrentBlock[Turn]))
            {
                CX++;
                VX = 1;
                VY = 0;
                Time = 0;
                State = 1;
            }// ブロックを回転させる
            else if (Input.GetKeyDown(KeyCode.Z))
            {
                if (!StageCell.Hit(CX, CY, CurrentBlock[(Turn + 1) % DROPPING_BLOCK_TURN_COUNT]))
                {
                    Turn = (Turn + 1) % DROPPING_BLOCK_TURN_COUNT;

                    GameObject[] cubes = GameObject.FindGameObjectsWithTag("MoveCube");
                    int cnt = 0;
                    for (int y = 0; y < CurrentBlock[Turn].GetYSize(); y++)
                    {
                        for (int x = 0; x < CurrentBlock[Turn].GetXSize(); x++)
                        {
                            if (CurrentBlock[Turn].Get(x, y) == '#')
                            {
                                cubes[cnt++].transform.localPosition = new Vector3(x, CurrentBlock[Turn].GetYSize() - y, 0);
                                //GameObject go = Instantiate(Cube2, new Vector3(x, CurrentBlock[Turn].GetYSize() - y, 0), Quaternion.identity);
                            }
                        }
                    }
                }
            }
            if (Input.GetKeyDown(KeyCode.DownArrow)) PrevDown = false;
        }

        // 移動状態の処理
        if (State == 1)
        {
            Time++;
            X = CX - VX * (1 - Time * 0.1f);
            Y = CY - VY * (1 - Time * 0.1f);

            emptyGameObject.transform.position= new Vector3( X, DROPPING_BLOCK_FIELD_BOTTOM - Y-1, 0);

            // 移動し終わったら入力状態へ
            if (Time == 10)
            {
                State = 0;
            }
        }

        // 落下判定状態
        if (State == 2)
        {
            // 落ちるブロックがまったくない場合には、消去判定状態へ移行する
            State = 4;

            // ステージ全体のセルを下から上に調べる
            for (int x = DROPPING_BLOCK_FIELD_LEFT; x < DROPPING_BLOCK_FIELD_RIGHT; x++)
            {
                for (int y = StageCell.GetYSize() - 1; y >= DROPPING_BLOCK_FIELD_TOP; y--)
                {
                    // 下に何もないブロックを捜す
                    if (StageCell.Get(x, y) == '+')//' ' && StageCell->Get( x, y-1 ) != ' ' )
                    {
                        // 下に何もないブロックがあったら、
                        // そのブロックと、それより上にある全てのブロックを下に移動する
                        for (; y >= DROPPING_BLOCK_FIELD_TOP; y--)
                        {
                            // セルがブロックかどうかを調べ、
                            // ブロックの場合には落ちるブロックとしてマークし
                            // 一段ずつ下に移動させる
                            char c = StageCell.Get(x, y - 1);

                            if (c != ' ')
                            {
                                StageCell.Set(x, y, c, true);//Get(c | 0x80));
                            }
                            else
                            {
                                StageCell.Set(x, y, ' ');
                            }
                        }

                        // タイマーを設定し落下状態に移行する
                        Time = 0;
                        State = 3;
                    }
                }
            }
        }

        // 落下状態
        if (State == 3)
        {
            Time++;

            // タイマーが一定値になったら、落下判定状態に移行し、
            // 落ちるボールのマークを解除する
            if (Time == 10)
            {
                for (int y = DROPPING_BLOCK_FIELD_TOP; y < StageCell.GetYSize(); y++)
                {
                    for (int x = DROPPING_BLOCK_FIELD_LEFT; x < DROPPING_BLOCK_FIELD_RIGHT; x++)
                    {
                        // 落ちるボールのマークを解除する
                        StageCell.Set(x, y, StageCell.Get(x, y), false);// StageCell.Get(x, y) & 0x7f);
                    }
                }
                GameObject[] objs2 = GameObject.FindGameObjectsWithTag("Finish");
                for (int i = 0; i < objs2.Length; ++i)
                {
                    Destroy(objs2[i]);
                }
                for (int y = 0; y < StageCell.GetYSize(); y++)
                {
                    for (int x = 0; x < StageCell.GetXSize(); x++)
                    {
                        if (StageCell.Get(x, y) == '#')
                        {
                            Instantiate(Cube, new Vector3(x, StageCell.GetYSize() - y, 0), Quaternion.identity);
                        }
                    }
                }
                State = 2;
            }
        }

        // 消去判定状態
        if (State == 4)
        {
            // ブロックがまったく消えなかった場合には、
            // 再出現状態に移行する
            State = 6;

            // ブロックが隙間なく揃った段を消す
            for (int y = DROPPING_BLOCK_FIELD_TOP; y < StageCell.GetYSize() - 1; y++)
            {
                int x;
                for (x = DROPPING_BLOCK_FIELD_LEFT; x < DROPPING_BLOCK_FIELD_RIGHT; x++)
                {
                    // 空のセルがある場合はループを抜け出す
                    if (StageCell.Get(x, y) == ' ') break;
                }

                // ブロックが揃った段がある場合の処理
                if (x >= DROPPING_BLOCK_FIELD_RIGHT)
                {
                    for (x = DROPPING_BLOCK_FIELD_LEFT; x < DROPPING_BLOCK_FIELD_RIGHT; x++)
                    {
                        StageCell.Set(x, y, '+');
                    }
                    // タイマーを設定し、消去状態へ移行する
                    Time = 0;
                    State = 5;
                }
            }
            GameObject[] objs = GameObject.FindGameObjectsWithTag("Finish");
            for (int y = 0; y < StageCell.GetYSize(); y++)
            {
                for (int x = 0; x < StageCell.GetXSize(); x++)
                {
                    if (StageCell.Get(x, y) == '+')
                    {
                        for(int i = 0; i < objs.Length; ++i)
                        {
                            float y2 = objs[i].transform.position.y;
                            if(y2 == StageCell.GetYSize() - y)
                            {
                                objs[i].tag = "EraseCube";
                            }
                        }
                        //Instantiate(Cube, new Vector3(x, StageCell.GetYSize() - y, 0), Quaternion.identity);
                    }
                }
            }
        }

        // 消去状態の処理
        if (State == 5)
        {
            Time++;
            // 一定時間になったらブロックを消す
            if (Time == 20)
            {
                GameObject[] objs = GameObject.FindGameObjectsWithTag("EraseCube");
                for(int i =0; i < objs.Length; ++i)
                {
                    Destroy(objs[i]);
                }
                // 落下判定状態に移行する
                State = 2;
            }
        }

        // 再出現状態
        if (State == 6)
        {
            Init(); // 新しいブロックを出現させる
        }
    }
}

Unityエディタでの作業

Resourcesを作り、その中にCube1(地形と結合前)とCube2(地形と結合後)の二つをつくります。マテリアルを2種類作って、結合前と結合後の色をわかるようにしておきましょう。空のゲームオブジェクトを作ってCTetorisu.csを張り付ければ完成です!

実行結果

テクスチャを使ったテトリス

Cubeを生成しないでテクスチャをOnGUIで描画したバージョンのテトリスのコードも紹介します。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;

public class CTetorisu : MonoBehaviour
{

    CCell StageCell = new CCell();
    CCell[] BlockCell = new CCell[DROPPING_BLOCK_PATTERN_COUNT];
    CCell[] CurrentBlock = new CCell[DROPPING_BLOCK_CELL_SIZE];
    CCell[] NextBlock = new CCell[DROPPING_BLOCK_PATTERN_COUNT];
    public float X, Y;
    int CX, CY, VX, VY;
    public int State; 
    int Time, DropTime, Turn;
    bool PrevDown;
	const int DROPPING_BLOCK_CELL_SIZE = 4;
	const int DROPPING_BLOCK_PATTERN_COUNT = 28;
	const int DROPPING_BLOCK_TURN_COUNT = 4;
	const int DROPPING_BLOCK_FIELD_LEFT = 1;
	const int DROPPING_BLOCK_FIELD_RIGHT = 11;
	const int DROPPING_BLOCK_FIELD_TOP = 1;
	const int DROPPING_BLOCK_FIELD_BOTTOM = 12;
	const int DROPPING_BLOCK_NEXT_LEFT = 12;
	// ブロックのパターン
	string[,] DroppingBlockPattern = new string[DROPPING_BLOCK_PATTERN_COUNT,DROPPING_BLOCK_TURN_COUNT]
	{
        {
            "    ",
            " ## ",
            " ## ",
            "    ",
        },
        {
            "    ",
            " ## ",
            " ## ",
            "    ",
        },
        {
            "    ",
            " ## ",
            " ## ",
            "    ",
        },
        {
            "    ",
            " ## ",
            " ## ",
            "    ",
        },

        {
            "    ",
            "####",
            "    ",
            "    ",
        },
        {
            " #  ",
            " #  ",
            " #  ",
            " #  ",
        },
        {
            "    ",
            "####",
            "    ",
            "    ",
        },
        {
            " #  ",
            " #  ",
            " #  ",
            " #  ",
        },

        {
            "    ",
            "##  ",
            " ## ",
            "    ",
        },
        {
            "  # ",
            " ## ",
            " #  ",
            "    ",
        },
        {          
            "    ",
            "##  ",
            " ## ",
            "    ",
        },
        {
            "  # ",
            " ## ",
            " #  ",
            "    ",
        },

        {
            " #  ",
            " ## ",
            "  # ",
            "    ",
        },
        {
            "    ",
            " ## ",
            "##  ",
            "    ",
        },
        {
            " #  ",
            " ## ",
            "  # ",
            "    ",
        },
        {
            "    ",
            " ## ",
            "##  ",
            "    ",
        },


        {
            " #  ",
            " #  ",
            " ## ",
            "    ",
        },
        {
            "    ",
            "### ",
            "#   ",
            "    ",
        },
        {
            "##  ",
            " #  ",
            " #  ",
            "    ",
        },
        {
            "#   ",
            "### ",
            "    ",
            "    ",
        },

        {
            " ## ",
            " #  ",
            " #  ",
            "    ",
        },
        {
            "    ",
            "### ",
            "  # ",
            "    ",
        },
        {
            " #  ",
            " #  ",
            "##  ",
            "    ",
        },
        {
            "    ",
            "### ",
            "#   ",
            "    ",
        },

        {
            " #  ",
            "### ",
            "    ",
            "    ",
        },
        {
            " #  ",
            " ## ",
            " #  ",
            "    ",
        },
        {
            "    ",
            "### ",
            " #  ",
            "    ",
        },
        {
            " #  ",
            "##  ",
            " #  ",
            "    ",
        }
    };

    void Init()
    {
        int rand = UnityEngine.Random.Range(0, 7);
        for(int i = 0; i < DROPPING_BLOCK_TURN_COUNT; ++i)
        {
            CurrentBlock[i] = NextBlock[rand*4+i];
        }
        
        NextBlock = BlockCell;//.OrderBy(i => Guid.NewGuid()).ToArray();
        CX = (DROPPING_BLOCK_FIELD_LEFT + DROPPING_BLOCK_FIELD_RIGHT - DROPPING_BLOCK_CELL_SIZE) / 2;
        CY = DROPPING_BLOCK_FIELD_TOP;
        X = CX;
        Y = CY;
        State = 0;
        DropTime = 0;
        Turn = 0;
        if (StageCell.Hit(CX, CY, CurrentBlock[Turn]))
        {
            State = 99999;
        }
        PrevDown = true;
    }
    //public GameObject Cube,Cube2;
    public Material CubeMat;
    private void Start()
    {
        string temp = string.Empty;
        for (int i = 0; i < DROPPING_BLOCK_PATTERN_COUNT; i++)
        {
            temp = string.Empty;
            BlockCell[i] = new CCell(DROPPING_BLOCK_CELL_SIZE, DROPPING_BLOCK_CELL_SIZE);
            for (int j = 0; j < DROPPING_BLOCK_TURN_COUNT; j++)
            {
                temp += DroppingBlockPattern[i, j];
            }
            BlockCell[i].Init(temp);
        }
        NextBlock = BlockCell;
        string[] str =
        {
            "                \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "=          =    \n",
            "================\n",
        };
        temp = string.Empty;
        for (int i = 0; i < str.Length; ++i)
        {
            temp += str[i];
        }
        StageCell = new CCell(temp, temp.Length);
        Init();
    }

    private void Update()
    {
        if (State == 0)
        {
            DropTime++;

            // レバーを下に入力するか、落下タイマーが一定値に達したら、ブロックを下げる
            if ((Input.GetKey(KeyCode.DownArrow) && !PrevDown) || DropTime == 60)
            {
                // セル座標を更新したときに、ブロックがステージの壁や他のブロックに
                // 当たっているか調べる
                if (StageCell.Hit(CX, CY + 1, CurrentBlock[Turn]))
                {
                    GameObject[] objs = GameObject.FindGameObjectsWithTag("MoveCube");
                    for(int i = 0; i < objs.Length; ++i)
                    {
                        objs[i].tag = "Finish";
                        objs[i].GetComponent<MeshRenderer>().material = CubeMat;
                    }
                    // 接触する場合には、ブロックのセルをステージのセルに合流する
                    StageCell.Merge(CX, CY, CurrentBlock[Turn]);

                    // 消去判定状態へ移行する
                    // 着地後のブロックを落としたいならStateを2の落下判定状態にする
                    State = 4;
                }
                else
                {
                    // 接触しない場合には、落下タイマーとセル座標を更新する
                    DropTime = 0;
                    CY++;
                    VX = 0;
                    VY = 1;
                    Time = 0;
                    State = 1;
                }
            }// ブロックを左へ移動させる
            else if (Input.GetKey(KeyCode.LeftArrow)
                && !StageCell.Hit(CX - 1, CY, CurrentBlock[Turn]))
            {
                CX--;
                VX = -1;
                VY = 0;
                Time = 0;
                State = 1;
            } //ブロックを右へ移動させる
            else if (Input.GetKey(KeyCode.RightArrow) && !StageCell.Hit(CX + 1, CY, CurrentBlock[Turn]))
            {
                CX++;
                VX = 1;
                VY = 0;
                Time = 0;
                State = 1;
            }// ブロックを回転させる
            else if (Input.GetKeyDown(KeyCode.Z))
            {
                if (!StageCell.Hit(CX, CY, CurrentBlock[(Turn + 1) % DROPPING_BLOCK_TURN_COUNT]))
                {
                    Turn = (Turn + 1) % DROPPING_BLOCK_TURN_COUNT;
                }
            }
            if (Input.GetKeyDown(KeyCode.DownArrow)) PrevDown = false;
        }

        // 移動状態の処理
        if (State == 1)
        {
            Time++;
            X = CX - VX * (1 - Time * 0.1f);
            Y = CY - VY * (1 - Time * 0.1f);

            //emptyGameObject.transform.position= new Vector3( X, DROPPING_BLOCK_FIELD_BOTTOM - Y-1, 0);

            // 移動し終わったら入力状態へ
            if (Time == 10)
            {
                State = 0;
            }
        }

        // 落下判定状態
        if (State == 2)
        {
            // 落ちるブロックがまったくない場合には、消去判定状態へ移行する
            State = 4;

            // ステージ全体のセルを下から上に調べる
            for (int x = DROPPING_BLOCK_FIELD_LEFT; x < DROPPING_BLOCK_FIELD_RIGHT; x++)
            {
                for (int y = StageCell.GetYSize() - 1; y >= DROPPING_BLOCK_FIELD_TOP; y--)
                {
                    // 下に何もないブロックを捜す
                    if (StageCell.Get(x, y) == '+')//' ' && StageCell->Get( x, y-1 ) != ' ' )
                    {
                        // 下に何もないブロックがあったら、
                        // そのブロックと、それより上にある全てのブロックを下に移動する
                        for (; y >= DROPPING_BLOCK_FIELD_TOP; y--)
                        {
                            // セルがブロックかどうかを調べ、
                            // ブロックの場合には落ちるブロックとしてマークし
                            // 一段ずつ下に移動させる
                            char c = StageCell.Get(x, y - 1);

                            if (c != ' ')
                            {
                                StageCell.Set(x, y, c, true);//Get(c | 0x80));
                            }
                            else
                            {
                                StageCell.Set(x, y, ' ');
                            }
                        }

                        // タイマーを設定し落下状態に移行する
                        Time = 0;
                        State = 3;
                    }
                }
            }
        }

        // 落下状態
        if (State == 3)
        {
            Time++;

            // タイマーが一定値になったら、落下判定状態に移行し、
            // 落ちるボールのマークを解除する
            if (Time == 10)
            {
                for (int y = DROPPING_BLOCK_FIELD_TOP; y < StageCell.GetYSize(); y++)
                {
                    for (int x = DROPPING_BLOCK_FIELD_LEFT; x < DROPPING_BLOCK_FIELD_RIGHT; x++)
                    {
                        // 落ちるボールのマークを解除する
                        StageCell.Set(x, y, StageCell.Get(x, y), false);// StageCell.Get(x, y) & 0x7f);
                    }
                }
                State = 2;
            }
        }

        // 消去判定状態
        if (State == 4)
        {
            // ブロックがまったく消えなかった場合には、
            // 再出現状態に移行する
            State = 6;

            // ブロックが隙間なく揃った段を消す
            for (int y = DROPPING_BLOCK_FIELD_TOP; y < StageCell.GetYSize() - 1; y++)
            {
                int x;
                for (x = DROPPING_BLOCK_FIELD_LEFT; x < DROPPING_BLOCK_FIELD_RIGHT; x++)
                {
                    // 空のセルがある場合はループを抜け出す
                    if (StageCell.Get(x, y) == ' ') break;
                }

                // ブロックが揃った段がある場合の処理
                if (x >= DROPPING_BLOCK_FIELD_RIGHT)
                {
                    for (x = DROPPING_BLOCK_FIELD_LEFT; x < DROPPING_BLOCK_FIELD_RIGHT; x++)
                    {
                        StageCell.Set(x, y, '+');
                    }
                    // タイマーを設定し、消去状態へ移行する
                    Time = 0;
                    State = 5;
                }
            }
        }

        // 消去状態の処理
        if (State == 5)
        {
            Time++;
            // 一定時間になったらブロックを消す
            if (Time == 20)
            {
                // 落下判定状態に移行する
                State = 2;
            }
        }

        // 再出現状態
        if (State == 6)
        {
            Init(); // 新しいブロックを出現させる
        }
    }
    public Texture aTexture;
    private void OnGUI()
    {
        int ey;
        for (ey = CY; ey < StageCell.GetYSize(); ey++)
        {
            if (StageCell.Hit(CX, ey + 1, CurrentBlock[Turn])) break;
        }

        if (Event.current.type == EventType.Repaint)
        {
            Graphics.DrawTexture(new Rect(0, 0, 32, 32), aTexture);
            // 周りのブロックを描画
            for (int y = 0; y < StageCell.GetYSize(); y++)
            {
                for (int x = 0; x < StageCell.GetXSize(); x++)
                {
                    char c = StageCell.Get(x, y);
                    switch (c)
                    {
                        // 結合したあとのブロックを描画
                        case '#':
                            Graphics.DrawTexture(new Rect(x*32, y*32, 32, 32), aTexture);
                            //DrawBox(x * 32, y * 32, x * 32 + 32, y * 32 + 32, GetColor(255, 0, 0), TRUE);
                            break;
                        // 消えるブロックの描画
                        case '+':
                            Graphics.DrawTexture(new Rect(x * 32, y * 32, 32, 32), aTexture);
                            //DrawBox(x * 32, y * 32, x * 32 + 32, y * 32 + 32, GetColor(0, 255, 0), TRUE);
                            break;
                        // 最初からある地形ブロックを描画
                        case '=':
                            Graphics.DrawTexture(new Rect(x * 32, y * 32, 32, 32), aTexture);
                            //DrawBox(x * 32, y * 32, x * 32 + 32, y * 32 + 32, GetColor(0, 0, 255), TRUE);
                            break;
                        default:
                            // 消える瞬間のブロック
                            if (StageCell.GetFlag(x, y))
                            {
                                Graphics.DrawTexture(new Rect(x * 32, y * 32, 32, 32), aTexture);
                                //DrawBox(x * 32, (y - 1 + Time * 0.1f) * 32,
                                //    (x + 1) * 32, (y - 1 + Time * 0.1f + 1) * 32,
                                //    Game->Color[0], TRUE);
                            }
                            break;
                    }
                }
            }
            for (int y = 0; y < DROPPING_BLOCK_CELL_SIZE; y++)
            {
                for (int x = 0; x < DROPPING_BLOCK_CELL_SIZE; x++)
                {
                    // 落ちているブロックを描画
                    if (State < 2 && CurrentBlock[Turn].Get(x, y) == '#')
                    {
                        // 予想地点の描画
                        //SetDrawBlendMode(DX_BLENDMODE_ALPHA, 85);
                        //DrawBox(CX * 32 + (32 * x), ey * 32 + (32 * y), CX * 32 + (32 * x) + 32, ey * 32 + (32 * y) + 32, GetColor(255, 255, 255), TRUE);
                        //SetDrawBlendMode(DX_BLENDMODE_NOBLEND, 255);
                        // 実際に落ちてるブロックの描画
                        Graphics.DrawTexture(new Rect((X+x) * 32, (Y+y) * 32, 32, 32), aTexture);
                        //DrawBox((X + x) * 32, (Y + y) * 32, (X + x + 1) * 32, (Y + y + 1) * 32, GetColor(255, 255, 255), TRUE);
                    }
                    // 次のブロックを描画
                    //if (NextBlock[0].Get(x, y) == '#')
                    //{
                    //    DrawBox(
                    //        (DROPPING_BLOCK_NEXT_LEFT + x) * 32 + 32, (1 + y) * 32,
                    //        (DROPPING_BLOCK_NEXT_LEFT + x) * 32 + 32 + 32, (1 + y) * 32 + 32, GetColor(255, 255, 255), TRUE);
                    //}
                }
            }
        }
    }
}