Unityでテトリスを作ってみよう
こんにちは!ジェイです。今回は久しぶりに1からゲームを作る講座で、みなさんがご存じの人気のゲームのテトリスの作り方を解説していきます。Unityの使い方をある程度知ってる方が対象になります。
テトリスに必要な要素
- ブロックの移動
- ブロックの回転(時計周り)
- ブロックの落下
- 揃った列を消す
- 消えた列を詰める
- 上までブロックが積みあがったらゲームオーバー
フィールドの状態を表す部分
盤面の状態を表す部分 Field[15, 23] 空白は0、壁は9です。ゲーム上では、縦20、横10になります。壁の外の0は表示されませんが、回転の補正問題に対応するために確保しておきます。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | |
0 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
1 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
2 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
3 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
4 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
5 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
6 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
7 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
8 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
9 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
10 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
11 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
12 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
13 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
14 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
15 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
16 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
17 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
18 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
19 | 0 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
20 | 0 | 0 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 0 | 0 |
21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
22 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
ブロックの状態を表す部分
ブロックの値は1で空白は0です。
0 | 1 | 2 | 3 | |
0 | 0 | 1 | 0 | 0 |
1 | 0 | 1 | 1 | 0 |
2 | 0 | 1 | 0 | 0 |
3 | 0 | 0 | 0 | 0 |
ブロックをフィールドに乗せて、移動できるなら、ブロックをいったん取り出して、場所を変えてもう一回乗せる。
固定ブロックの状態を表す部分
stage[12,21]
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
1 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
2 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
3 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
4 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
5 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
6 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
7 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
8 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
9 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
10 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
11 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
12 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
13 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
14 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
15 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
16 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
17 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
18 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
19 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
20 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 |
ブロックが底についたり他のブロックに当たって動けなくなったら、盤面上のブロックをStageに保存して、固定ブロックにします。それからフィールドを改めて描画し、ブロックは初期位置で再生成します。
表示用ブロックを並べる
空のGameObjectを作成しGameDirectorという名前にします。(F2で名前変更)その後、CGameManager.csをアタッチする。
ブロックの作成
3DObject→Cubeで作成した後にRigidbodyをアタッチして以下の様に設定して、プレハブ化します。HierarchyのCubeはいらないので削除してしまいましょう。

マテリアル作成で色を付ける
Create→Materialでマテリアルを以下のように作成します。透明感を出すためにRendering ModeをFadeに変更しましょう。RGBAのAの部分が透明度なのでそこで調整してください。
- Color1 落ちるブロックの色(黄色)
- Color2 何もない場所の色(青色)
- Color3 壁の色(灰色)
ブロックを並べるスクリプト
C#スクリプトを作成して、CGameManagerと名前を変えます。以下のスクリプトでは横16,縦23個ずつInstantiateでプレハブ化したBlkObjを生成するという処理です。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CGameManager : MonoBehaviour
{
public GameObject BlkObj;
void Start()
{
for(int i = 0; i < 16; ++i)
{
for(int j = 0; j < 23; ++j)
{
Instantiate(BlkObj, new Vector3(i * 1.0f, j * -1.0f, 0), Quaternion.identity);
}
}
}
}
CGameManager.csをGameDirectorにアタッチします。

カメラのTransformも以下の様に変更します。

実行結果

ステージの表示
ブロックに色を付けてステージを作ります。ブロック自身が自分の座標に該当する配列を読み、読み取った値から色を利用してブロックの色を変化させます。CGameManager.csを以下の様に書き換えましょう。
Fieldは横10ですが、壁に2つ、のりしろに4つの合計16つ
縦は20ですが、壁に1つ、のりしろに2つの合計23つ確保します。
Stageはブロックが落下して止まった時、その状況を記録する配列です。
配列にデータを入れる
次に用意した配列にデータを入れるBlockCは以下になります。Prefab化したBlockにアタッチしましょう。Y軸はマイナスの値が返ってくるので、Mathf.Absで絶対値にして、マイナスを取り除いています。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CBlockController: MonoBehaviour
{
// GameDirectorの格納
GameObject GD;
GameC GC;
// マテリアル
public Material[] MT;
// 座標
int PosX, PosY;
// ブロックの色
int BlkCol;
// 時間
float Delta;
void Start()
{
// 自身の座標を取得する
PosX = (int)transform.position.x;
PosY = (int)Mathf.Abs(transform.position.y);
GD = GameObject.Find("GameDirector");
GC = GD.GetComponent<GameC>();
}
void Update()
{
// 時間の加算
Delta += Time.deltaTime;
if(Delta > 0.1f)
{
// 座標に該当する番目の値を読み込む
BlkCol = GC.Field[PosX, PosY];
// 色を変化させる
GetComponent<Renderer>().material = MT[BlkCol];
// 時間をリセット
Delta = 0;
}
}
}

フィールドのスクリプト
CGameMain.csもFieldの値に対応するように以下の様に書き換えましょう。特筆すべき点は、最初のInstantiateで生成しているfor文は左端、右端、下の2マスずつのりしろがあるので、その部分は表示しないように引いています。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CGameManager: MonoBehaviour
{
const int BLK_WITH = 16;
const int BLK_HEIGHT = 23;
const int ONE_BLK_NUM = 4;
public GameObject BlkObj;
// フィールドの状態
public int[,] Field = new int[BLK_WITH, BLK_HEIGHT];
// フィールドの状態を保存(ブロックの固定)
public int[,] Stage = new int[BLK_WITH, BLK_HEIGHT];
// ブロックを表現する配列
int[,] Blk = new int[ONE_BLK_NUM, ONE_BLK_NUM];
// ブロックを一時的に保存する配列(回転の時に使う)
int[,] Blktmp = new int[ONE_BLK_NUM, ONE_BLK_NUM];
void Start()
{
for(int i = 0; i < BLK_WITH; ++i)
{
for(int j = 0; j < BLK_HEIGHT; ++j)
{
// 壁にする部分は9
if(i == 2 || i == 13 || j == 20)
{
Stage[i, j] = 9;
}
else
{
Stage[i, j] = 0;
}
}
}
for (int i = 0; i < BLK_WITH; ++i)
{
for (int j = 0; j < BLK_HEIGHT; ++j)
{
Field[i, j] = Stage[i, j];
}
}
// 左上から右下にブロックを生成する(-2でのりしろを表示させないようにする)
for (int i = 2; i < BLK_WITH-2; ++i)
{
for(int j = 0; j < BLK_HEIGHT-2; ++j)
{
Instantiate(BlkObj, new Vector3(i * 1.0f, j * -1.0f, 0f), Quaternion.identity);
}
}
}
}
実行結果

落下ブロックの表示
次は落下するブロックを表示させます。CGameMain.csを以下の様に書き換えてください。変更点に色をつけてます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CGameManager : MonoBehaviour
{
const int BLK_WITH = 16;
const int BLK_HEIGHT = 23;
const int ONE_BLK_NUM = 4;
public GameObject BlkObj;
int BlkNo;
// フィールドの状態
public int[,] Field = new int[BLK_WITH, BLK_HEIGHT];
// フィールドの状態を保存(ブロックの固定)
public int[,] Stage = new int[BLK_WITH, BLK_HEIGHT];
// ブロックを表現する配列
int[,] Blk = new int[ONE_BLK_NUM, ONE_BLK_NUM];
// ブロックを一時的に保存する配列(回転の時に使う)
int[,] Blktmp = new int[ONE_BLK_NUM, ONE_BLK_NUM];
void Start()
{
for(int i = 0; i < BLK_WITH; ++i)
{
for(int j = 0; j < BLK_HEIGHT; ++j)
{
// 壁にする部分は9
if(i == 2 || i == 13 || j == 20)
{
Stage[i, j] = 9;
}
else
{
Stage[i, j] = 0;
}
}
}
for (int i = 0; i < BLK_WITH; ++i)
{
for (int j = 0; j < BLK_HEIGHT; ++j)
{
Field[i, j] = Stage[i, j];
}
}
// 左上から右下にブロックを生成する(-2でのりしろを表示させないようにする)
for (int i = 2; i < BLK_WITH-2; ++i)
{
for(int j = 0; j < BLK_HEIGHT-2; ++j)
{
Instantiate(BlkObj, new Vector3(i * 1.0f, j * -1.0f, 0f), Quaternion.identity);
}
}
MakeBlock();
}
void MakeBlock()
{
// ランダムに0~6までの乱数を発生
BlkNo = Random.Range(0, 7);
switch(BlkNo)
{
case 0:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,0,0},
{0,1,0,0},
};
break;
case 1:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,0,1,0},
{0,0,1,0},
};
break;
case 2:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,1,0},
{0,0,0,0},
};
break;
case 3:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{1,1,1,0},
{0,0,0,0},
};
break;
case 4:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{0,1,1,0},
{0,0,1,0},
};
break;
case 5:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,0,1,0},
{0,1,1,0},
{0,1,0,0},
};
break;
case 6:
Blk = new int[4, 4]
{
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
};
break;
default:
break;
}
// フィールドにブロックを加える
for(int i = 0; i < 4; ++i)
{
for(int j = 0; j < 4; ++j)
{
Field[i + 4, j] = Stage[i + 4, j] + Blk[i, j];
}
}
}
}
実行結果

現在の状況では落下ブロックが表示されるだけで、実際に落下しません。次は落下する処理を加えていきます。
ブロックの落下
CGameManager.csにMoveBlock関数を追加して、ブロックを落下する処理を追加します。
処理としては
- Y座標に1加える
- 現在地にあるブロックを消す
- 新しい座標にブロックを更新して
- 再度フィールドにブロックを加える
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CGameManager : MonoBehaviour
{
const int BLK_WITH = 16;
const int BLK_HEIGHT = 23;
const int ONE_BLK_NUM = 4;
public GameObject BlkObj;
int BlkNo;
// フィールドの状態
public int[,] Field = new int[BLK_WITH, BLK_HEIGHT];
// フィールドの状態を保存(ブロックの固定)
public int[,] Stage = new int[BLK_WITH, BLK_HEIGHT];
// ブロックを表現する配列
public int[,] Blk = new int[ONE_BLK_NUM, ONE_BLK_NUM];
// ブロックを一時的に保存する配列(回転の時に使う)
int[,] Blktmp = new int[ONE_BLK_NUM, ONE_BLK_NUM];
// 経過時間
public float Delta;
// 間隔
float Span = 0.5f;
// ブロックの座標(初期位置)
int X = 4, Y = 0;
void Start()
{
for(int i = 0; i < BLK_WITH; ++i)
{
for(int j = 0; j < BLK_HEIGHT; ++j)
{
// 壁にする部分は9
if(i == 2 || i == 13 || j == 20)
{
Stage[i, j] = 9;
}
else
{
Stage[i, j] = 0;
}
}
}
for (int i = 0; i < BLK_WITH; ++i)
{
for (int j = 0; j < BLK_HEIGHT; ++j)
{
Field[i, j] = Stage[i, j];
}
}
// 左上から右下にブロックを生成する(-2でのりしろを表示させないようにする)
for (int i = 2; i < BLK_WITH-2; ++i)
{
for(int j = 0; j < BLK_HEIGHT-2; ++j)
{
Instantiate(BlkObj, new Vector3(i * 1.0f, j * -1.0f, 0f), Quaternion.identity);
}
}
MakeBlock();
}
void Update()
{
Delta += Time.deltaTime;
if (Delta > Span)
{
MoveBlock(X, Y + 1);
Delta = 0;
}
}
void MakeBlock()
{
// ランダムに0~6までの乱数を発生
BlkNo = Random.Range(0, 7);
switch(BlkNo)
{
case 0:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,0,0},
{0,1,0,0},
};
break;
case 1:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,0,1,0},
{0,0,1,0},
};
break;
case 2:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,1,0},
{0,0,0,0},
};
break;
case 3:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{1,1,1,0},
{0,0,0,0},
};
break;
case 4:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{0,1,1,0},
{0,0,1,0},
};
break;
case 5:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,0,1,0},
{0,1,1,0},
{0,1,0,0},
};
break;
case 6:
Blk = new int[4, 4]
{
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
};
break;
default:
break;
}
// フィールドにブロックを加える
for(int i = 0; i < 4; ++i)
{
for(int j = 0; j < 4; ++j)
{
Field[i + 4, j] = Stage[i + 4, j] + Blk[i, j];
}
}
}
void MoveBlock(int x, int y)
{
// ブロックを一旦消す
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] -= Blk[i, j];
}
}
X = x;
Y = y;
// ブロックを追加
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] += Blk[i, j];
}
}
}
}
実行結果
ブロックが落下するようになりました。

ブロックの停止
前回までの処理でブロックが落下するようになりましたが、下まで来ても停止しませんでしたので、停止する処理を追加します。CheckBlock関数で移動できるか調べてから可能なら移動します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CGameManager : MonoBehaviour
{
const int BLK_WITH = 16;
const int BLK_HEIGHT = 23;
const int ONE_BLK_NUM = 4;
public GameObject BlkObj;
int BlkNo;
// フィールドの状態
public int[,] Field = new int[BLK_WITH, BLK_HEIGHT];
// フィールドの状態を保存(ブロックの固定)
public int[,] Stage = new int[BLK_WITH, BLK_HEIGHT];
// ブロックを表現する配列
public int[,] Blk = new int[ONE_BLK_NUM, ONE_BLK_NUM];
// ブロックを一時的に保存する配列(回転の時に使う)
int[,] Blktmp = new int[ONE_BLK_NUM, ONE_BLK_NUM];
public float Delta;
// 間隔
float Span = 0.5f;
// ブロックの座標(初期位置)
int X = 4, Y = 0;
void Start()
{
for(int i = 0; i < BLK_WITH; ++i)
{
for(int j = 0; j < BLK_HEIGHT; ++j)
{
// 壁にする部分は9
if(i == 2 || i == 13 || j == 20)
{
Stage[i, j] = 9;
}
else
{
Stage[i, j] = 0;
}
}
}
for (int i = 0; i < BLK_WITH; ++i)
{
for (int j = 0; j < BLK_HEIGHT; ++j)
{
Field[i, j] = Stage[i, j];
}
}
// 左上から右下にブロックを生成する(-2でのりしろを表示させないようにする)
for (int i = 2; i < BLK_WITH-2; ++i)
{
for(int j = 0; j < BLK_HEIGHT-2; ++j)
{
Instantiate(BlkObj, new Vector3(i * 1.0f, j * -1.0f, 0f), Quaternion.identity);
}
}
MakeBlock();
}
void Update()
{
Delta += Time.deltaTime;
if (Delta > Span)
{
if (CheckBlock(X, Y + 1))
{
MoveBlock(X, Y + 1);
Delta = 0;
}
}
}
void MakeBlock()
{
// ランダムに0~6までの乱数を発生
BlkNo = Random.Range(0, 7);
switch(BlkNo)
{
case 0:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,0,0},
{0,1,0,0},
};
break;
case 1:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,0,1,0},
{0,0,1,0},
};
break;
case 2:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,1,0},
{0,0,0,0},
};
break;
case 3:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{1,1,1,0},
{0,0,0,0},
};
break;
case 4:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{0,1,1,0},
{0,0,1,0},
};
break;
case 5:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,0,1,0},
{0,1,1,0},
{0,1,0,0},
};
break;
case 6:
Blk = new int[4, 4]
{
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
};
break;
default:
break;
}
// フィールドにブロックを加える
for(int i = 0; i < 4; ++i)
{
for(int j = 0; j < 4; ++j)
{
Field[i + 4, j] = Stage[i + 4, j] + Blk[i, j];
}
}
}
void MoveBlock(int x, int y)
{
// ブロックを一旦消す
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] -= Blk[i, j];
}
}
X = x;
Y = y;
// ブロックを追加
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] += Blk[i, j];
}
}
}
bool CheckBlock(int x, int y)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
// ブロックがあって
if(Blk[i, j] != 0)
{
// 移動予定地もブロックなら
if(Stage[x+i,y+j] != 0)
{
// 移動できない
return false;
}
}
}
}
// 重なってないので移動できる
return true;
}
}
実行結果

ブロックの左右移動
Update関数に左右の移動できるかチェックした後可能なら移動します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CGameManager: MonoBehaviour
{
const int BLK_WITH = 16;
const int BLK_HEIGHT = 23;
const int ONE_BLK_NUM = 4;
public GameObject BlkObj;
int BlkNo;
// フィールドの状態
public int[,] Field = new int[BLK_WITH, BLK_HEIGHT];
// フィールドの状態を保存(ブロックの固定)
public int[,] Stage = new int[BLK_WITH, BLK_HEIGHT];
// ブロックを表現する配列
public int[,] Blk = new int[ONE_BLK_NUM, ONE_BLK_NUM];
// ブロックを一時的に保存する配列(回転の時に使う)
int[,] Blktmp = new int[ONE_BLK_NUM, ONE_BLK_NUM];
public float Delta;
// 間隔
float Span = 0.5f;
// ブロックの座標(初期位置)
int X = 4, Y = 0;
void Start()
{
for(int i = 0; i < BLK_WITH; ++i)
{
for(int j = 0; j < BLK_HEIGHT; ++j)
{
// 壁にする部分は9
if(i == 2 || i == 13 || j == 20)
{
Stage[i, j] = 9;
}
else
{
Stage[i, j] = 0;
}
}
}
for (int i = 0; i < BLK_WITH; ++i)
{
for (int j = 0; j < BLK_HEIGHT; ++j)
{
Field[i, j] = Stage[i, j];
}
}
// 左上から右下にブロックを生成する(-2でのりしろを表示させないようにする)
for (int i = 2; i < BLK_WITH-2; ++i)
{
for(int j = 0; j < BLK_HEIGHT-2; ++j)
{
Instantiate(BlkObj, new Vector3(i * 1.0f, j * -1.0f, 0f), Quaternion.identity);
}
}
MakeBlock();
}
void Update()
{
Delta += Time.deltaTime;
if (Delta > Span)
{
if (CheckBlock(X, Y + 1))
{
MoveBlock(X, Y + 1);
Delta = 0;
}
}
// 右へ移動する
if(Input.GetKey(KeyCode.RightArrow) && CheckBlock(X - 1, Y))
{
MoveBlock(X - 1, Y);
}
// 左へ移動する
if (Input.GetKey(KeyCode.LeftArrow) && CheckBlock(X + 1, Y))
{
MoveBlock(X + 1, Y);
}
}
void MakeBlock()
{
// ランダムに0~6までの乱数を発生
BlkNo = Random.Range(0, 7);
switch(BlkNo)
{
case 0:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,0,0},
{0,1,0,0},
};
break;
case 1:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,0,1,0},
{0,0,1,0},
};
break;
case 2:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,1,0},
{0,0,0,0},
};
break;
case 3:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{1,1,1,0},
{0,0,0,0},
};
break;
case 4:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{0,1,1,0},
{0,0,1,0},
};
break;
case 5:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,0,1,0},
{0,1,1,0},
{0,1,0,0},
};
break;
case 6:
Blk = new int[4, 4]
{
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
};
break;
default:
break;
}
// フィールドにブロックを加える
for(int i = 0; i < 4; ++i)
{
for(int j = 0; j < 4; ++j)
{
Field[i + 4, j] = Stage[i + 4, j] + Blk[i, j];
}
}
}
void MoveBlock(int x, int y)
{
// ブロックを一旦消す
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] -= Blk[i, j];
}
}
X = x;
Y = y;
// ブロックを追加
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] += Blk[i, j];
}
}
}
bool CheckBlock(int x, int y)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
// ブロックがあって
if(Blk[i, j] != 0)
{
// 移動予定地もブロックなら
if(Stage[x+i,y+j] != 0)
{
// 移動できない
return false;
}
}
}
}
// 重なってないので移動できる
return true;
}
}
実行結果

ブロックの生成
ブロックが停止したら、動けなくなったブロックを保存したら、次のブロックを生成し落下させます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CGameManager : MonoBehaviour
{
const int BLK_WITH = 16;
const int BLK_HEIGHT = 23;
const int ONE_BLK_NUM = 4;
public GameObject BlkObj;
int BlkNo;
// フィールドの状態
public int[,] Field = new int[BLK_WITH, BLK_HEIGHT];
// フィールドの状態を保存(ブロックの固定)
public int[,] Stage = new int[BLK_WITH, BLK_HEIGHT];
// ブロックを表現する配列
public int[,] Blk = new int[ONE_BLK_NUM, ONE_BLK_NUM];
// ブロックを一時的に保存する配列(回転の時に使う)
int[,] Blktmp = new int[ONE_BLK_NUM, ONE_BLK_NUM];
public float Delta;
// 間隔
float Span = 0.5f;
// ブロックの座標(初期位置)
const int StartX = 5;
int X = StartX, Y = 0;
void Start()
{
// 壁の設定
for(int i = 0; i < BLK_WITH; ++i)
{
for(int j = 0; j < BLK_HEIGHT; ++j)
{
// 壁にする部分は9
if(i == 2 || i == 13 || j == 20)
{
Stage[i, j] = 9;
}
else
{
Stage[i, j] = 0;
}
}
}
// ステージデータを描く
for (int i = 2; i < 14; ++i)
{
for (int j = 0; j < 21; ++j)
{
Field[i, j] = Stage[i, j];
}
}
// 左上から右下にブロックを生成する(-2でのりしろを表示させないようにする)
for (int i = 2; i < BLK_WITH-2; ++i)
{
for(int j = 0; j < BLK_HEIGHT-2; ++j)
{
Instantiate(BlkObj, new Vector3(i * 1.0f, j * -1.0f, 0f), Quaternion.identity);
}
}
// ブロックの作成
MakeBlock();
// フィールドを描画
Render();
}
void Update()
{
Delta += Time.deltaTime;
if (Delta > Span)
{
// 下に移動できたなら
if (CheckBlock(X, Y + 1))
{
// 下に移動する
MoveBlock(X, Y + 1);
}
else // 下に移動できなかったら
{
// ブロックをロックする
LockBlk();
// 新しいブロックを生成する
MakeBlock();
// 描画しなおす
Render();
}
// 経過時間の初期化
Delta = 0;
}
// 右へ移動する
if(Input.GetKey(KeyCode.RightArrow) && CheckBlock(X - 1, Y))
{
MoveBlock(X - 1, Y);
}
// 左へ移動する
if (Input.GetKey(KeyCode.LeftArrow) && CheckBlock(X + 1, Y))
{
MoveBlock(X + 1, Y);
}
}
void MakeBlock()
{
// ランダムに0~6までの乱数を発生
BlkNo = Random.Range(0, 7);
switch(BlkNo)
{
case 0:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,0,0},
{0,1,0,0},
};
break;
case 1:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,0,1,0},
{0,0,1,0},
};
break;
case 2:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,1,0},
{0,0,0,0},
};
break;
case 3:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{1,1,1,0},
{0,0,0,0},
};
break;
case 4:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{0,1,1,0},
{0,0,1,0},
};
break;
case 5:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,0,1,0},
{0,1,1,0},
{0,1,0,0},
};
break;
case 6:
Blk = new int[4, 4]
{
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
};
break;
default:
break;
}
// ブロックを初期位置にする
X = StartX;
Y = 0;
// フィールドにブロックを加える
for(int i = 0; i < 4; ++i)
{
for(int j = 0; j < 4; ++j)
{
Field[i + StartX, j] = Stage[i + StartX, j] + Blk[i, j];
}
}
}
void MoveBlock(int x, int y)
{
// ブロックを一旦消す
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] -= Blk[i, j];
}
}
X = x;
Y = y;
// ブロックを追加
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] += Blk[i, j];
}
}
}
bool CheckBlock(int x, int y)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
// ブロックがあって
if(Blk[i, j] != 0)
{
// 移動予定地もブロックなら
if(Stage[x+i,y+j] != 0)
{
// 移動できない
return false;
}
}
}
}
// 重なってないので移動できる
return true;
}
void LockBlk()
{
for (int i = 2; i < 14; i++)
{
for (int j = 0; j < 21; j++)
{
Stage[i, j] = Field[i, j];
}
}
}
void Render()
{
// ステージを描く
for (int i = 2; i < 14; i++)
{
for (int j = 0; j < 21; j++)
{
Field[i, j] = Stage[i, j];
}
}
// ブロックを追加する
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Field[i + X, j + Y] = Stage[i + X, i + Y] + Blk[i, j];
}
}
}
}
実行結果

ブロックの回転
次にスペースキーを押すとブロックが回転するようにします。
回転の考え方。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CGameManager : MonoBehaviour
{
const int BLK_WITH = 16;
const int BLK_HEIGHT = 23;
const int ONE_BLK_NUM = 4;
public GameObject BlkObj;
int BlkNo;
// フィールドの状態
public int[,] Field = new int[BLK_WITH, BLK_HEIGHT];
// フィールドの状態を保存(ブロックの固定)
public int[,] Stage = new int[BLK_WITH, BLK_HEIGHT];
// ブロックを表現する配列
public int[,] Blk = new int[ONE_BLK_NUM, ONE_BLK_NUM];
// ブロックを一時的に保存する配列(回転の時に使う)
int[,] Blktmp = new int[ONE_BLK_NUM, ONE_BLK_NUM];
public float Delta;
// 間隔
float Span = 0.5f;
// ブロックの座標(初期位置)
const int StartX = 5;
int X = StartX, Y = 0;
void Start()
{
// 壁の設定
for(int i = 0; i < BLK_WITH; ++i)
{
for(int j = 0; j < BLK_HEIGHT; ++j)
{
// 壁にする部分は9
if(i == 2 || i == 13 || j == 20)
{
Stage[i, j] = 9;
}
else
{
Stage[i, j] = 0;
}
}
}
// ステージデータを描く
for (int i = 2; i < 14; ++i)
{
for (int j = 0; j < 21; ++j)
{
Field[i, j] = Stage[i, j];
}
}
// 左上から右下にブロックを生成する(-2でのりしろを表示させないようにする)
for (int i = 2; i < BLK_WITH-2; ++i)
{
for(int j = 0; j < BLK_HEIGHT-2; ++j)
{
Instantiate(BlkObj, new Vector3(i * 1.0f, j * -1.0f, 0f), Quaternion.identity);
}
}
// ブロックの作成
MakeBlock();
// フィールドを描画
Render();
}
float TotalTime = 0.0f;
void Update()
{
Delta += Time.deltaTime;
if (Delta > Span)
{
// 下に移動できたなら
if (CheckBlock(X, Y + 1))
{
// 下に移動する
MoveBlock(X, Y + 1);
}
else // 下に移動できなかったら
{
// ブロックをロックする
LockBlk();
// 新しいブロックを生成する
MakeBlock();
// 描画しなおす
Render();
}
// 経過時間の初期化
Delta = 0;
}
TotalTime += Time.deltaTime;
// 右へ移動する
if (Input.GetKey(KeyCode.RightArrow) && CheckBlock(X - 1, Y) && 0.25f < TotalTime)
{
MoveBlock(X - 1, Y);
TotalTime = 0.0f;
}
// 左へ移動する
if (Input.GetKey(KeyCode.LeftArrow) && CheckBlock(X + 1, Y) && 0.25f < TotalTime)
{
MoveBlock(X + 1, Y);
TotalTime = 0.0f;
}
// 回転させる
if(Input.GetKeyDown(KeyCode.Space))
{
TrunBlock();
}
}
void MakeBlock()
{
// ランダムに0~6までの乱数を発生
BlkNo = Random.Range(0, 7);
switch(BlkNo)
{
case 0:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,0,0},
{0,1,0,0},
};
break;
case 1:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,0,1,0},
{0,0,1,0},
};
break;
case 2:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,1,0},
{0,0,0,0},
};
break;
case 3:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{1,1,1,0},
{0,0,0,0},
};
break;
case 4:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{0,1,1,0},
{0,0,1,0},
};
break;
case 5:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,0,1,0},
{0,1,1,0},
{0,1,0,0},
};
break;
case 6:
Blk = new int[4, 4]
{
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
};
break;
default:
break;
}
// ブロックを初期位置にする
X = StartX;
Y = 0;
// フィールドにブロックを加える
for(int i = 0; i < 4; ++i)
{
for(int j = 0; j < 4; ++j)
{
Field[i + StartX, j] = Stage[i + StartX, j] + Blk[i, j];
}
}
}
void MoveBlk(int x, int y)
{
// ブロックを一旦消す
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] -= Blk[i, j];
}
}
X = x;
Y = y;
// ブロックを追加
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] += Blk[i, j];
}
}
}
bool CheckBlock(int x, int y)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
// ブロックがあって
if(Blk[i, j] != 0)
{
// 移動予定地もブロックなら
if(Stage[x+i,y+j] != 0)
{
// 移動できない
return false;
}
}
}
}
// 重なってないので移動できる
return true;
}
void LockBlk()
{
for (int i = 2; i < 14; i++)
{
for (int j = 0; j < 21; j++)
{
Stage[i, j] = Field[i, j];
}
}
}
void Render()
{
// ステージを描く
for (int i = 2; i < 14; i++)
{
for (int j = 0; j < 21; j++)
{
Field[i, j] = Stage[i, j];
}
}
// ブロックを追加する
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Field[i + X, j + Y] = Stage[i + X, i + Y] + Blk[i, j];
}
}
}
bool TrunBlock()
{
// ブロックを一時的に保存
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blktmp[i, j] = Blk[i, j];
}
}
// ブロックを回転させる(本来の座標XYをひっくり返して3を引けば回転する)
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blk[i, j] = Blktmp[3 - j, i];
}
}
// 回転先に重なりがあれば元に戻す
if(!CheckBlock(X,Y))
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blk[i, j] = Blktmp[i, j];
}
}
return true;
}
// 重なったブロックを消す
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Field[i + X, j + Y] -= Blktmp[i, j];
Field[i + X, j + Y] -= Blk[i, j];
}
}
// 重なってない場合は描画する
Render();
return false;
}
}
実行結果

ブロックを消す
次に一行揃ったらブロックを消す処理を追加します。EraseBlockで横1列を探索して、1列でも揃ってれば、1段おろして、さらに確認してを繰り返して、揃ってる列が無ければ、抜けるという処理になってます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CGameManager : MonoBehaviour
{
const int BLK_WITH = 16;
const int BLK_HEIGHT = 23;
const int ONE_BLK_NUM = 4;
public GameObject BlkObj;
int BlkNo;
// フィールドの状態
public int[,] Field = new int[BLK_WITH, BLK_HEIGHT];
// フィールドの状態を保存(ブロックの固定)
public int[,] Stage = new int[BLK_WITH, BLK_HEIGHT];
// ブロックを表現する配列
public int[,] Blk = new int[ONE_BLK_NUM, ONE_BLK_NUM];
// ブロックを一時的に保存する配列(回転の時に使う)
int[,] Blktmp = new int[ONE_BLK_NUM, ONE_BLK_NUM];
public float Delta;
// 間隔
float Span = 0.5f;
// ブロックの座標(初期位置)
const int StartX = 5;
int X = StartX, Y = 0;
void Start()
{
// 壁の設定
for(int i = 0; i < BLK_WITH; ++i)
{
for(int j = 0; j < BLK_HEIGHT; ++j)
{
// 壁にする部分は9
if(i == 2 || i == 13 || j == 20)
{
Stage[i, j] = 9;
}
else
{
Stage[i, j] = 0;
}
}
}
// ステージデータを描く
for (int i = 2; i < 14; ++i)
{
for (int j = 0; j < 21; ++j)
{
Field[i, j] = Stage[i, j];
}
}
// 左上から右下にブロックを生成する(-2でのりしろを表示させないようにする)
for (int i = 2; i < BLK_WITH-2; ++i)
{
for(int j = 0; j < BLK_HEIGHT-2; ++j)
{
Instantiate(BlkObj, new Vector3(i * 1.0f, j * -1.0f, 0f), Quaternion.identity);
}
}
// ブロックの作成
MakeBlock();
// フィールドを描画
Render();
}
float TotalTime = 0.0f;
void Update()
{
Delta += Time.deltaTime;
if (Delta > Span)
{
// 下に移動できたなら
if (CheckBlock(X, Y + 1))
{
// 下に移動する
MoveBlock(X, Y + 1);
}
else // 下に移動できなかったら
{
// ブロックをロックする
LockBlk();
// 1行揃ったら消す
EraseBlock();
// 新しいブロックを生成する
MakeBlock();
// 描画しなおす
Render();
}
// 経過時間の初期化
Delta = 0;
}
TotalTime += Time.deltaTime;
// 右へ移動する
if (Input.GetKey(KeyCode.RightArrow) && CheckBlock(X - 1, Y) && 0.25f < TotalTime)
{
MoveBlock(X - 1, Y);
TotalTime = 0.0f;
}
// 左へ移動する
if (Input.GetKey(KeyCode.LeftArrow) && CheckBlock(X + 1, Y) && 0.25f < TotalTime)
{
MoveBlock(X + 1, Y);
TotalTime = 0.0f;
}
// 回転させる
if(Input.GetKeyDown(KeyCode.Space))
{
TrunBlock();
}
}
void MakeBlock()
{
// ランダムに0~6までの乱数を発生
BlkNo = Random.Range(0, 7);
switch(BlkNo)
{
case 0:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,0,0},
{0,1,0,0},
};
break;
case 1:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,0,1,0},
{0,0,1,0},
};
break;
case 2:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,1,0},
{0,0,0,0},
};
break;
case 3:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{1,1,1,0},
{0,0,0,0},
};
break;
case 4:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{0,1,1,0},
{0,0,1,0},
};
break;
case 5:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,0,1,0},
{0,1,1,0},
{0,1,0,0},
};
break;
case 6:
Blk = new int[4, 4]
{
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
};
break;
default:
break;
}
// ブロックを初期位置にする
X = StartX;
Y = 0;
// フィールドにブロックを加える
for(int i = 0; i < 4; ++i)
{
for(int j = 0; j < 4; ++j)
{
Field[i + StartX, j] = Stage[i + StartX, j] + Blk[i, j];
}
}
}
void MoveBlock(int x, int y)
{
// ブロックを一旦消す
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] -= Blk[i, j];
}
}
X = x;
Y = y;
// ブロックを追加
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] += Blk[i, j];
}
}
}
bool CheckBlock(int x, int y)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
// ブロックがあって
if(Blk[i, j] != 0)
{
// 移動予定地もブロックなら
if(Stage[x+i,y+j] != 0)
{
// 移動できない
return false;
}
}
}
}
// 重なってないので移動できる
return true;
}
void LockBlk()
{
for (int i = 2; i < 14; i++)
{
for (int j = 0; j < 21; j++)
{
Stage[i, j] = Field[i, j];
}
}
}
void Render()
{
// ステージを描く
for (int i = 2; i < 14; i++)
{
for (int j = 0; j < 21; j++)
{
Field[i, j] = Stage[i, j];
}
}
// ブロックを追加する
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Field[i + X, j + Y] = Stage[i + X, i + Y] + Blk[i, j];
}
}
}
bool TrunBlock()
{
// ブロックを一時的に保存
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blktmp[i, j] = Blk[i, j];
}
}
// ブロックを回転させる(本来の座標XYをひっくり返して3を引けば回転する)
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blk[i, j] = Blktmp[3 - j, i];
}
}
// 回転先に重なりがあれば元に戻す
if(!CheckBlock(X,Y))
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blk[i, j] = Blktmp[i, j];
}
}
return true;
}
// 重なったブロックを消す
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Field[i + X, j + Y] -= Blktmp[i, j];
Field[i + X, j + Y] -= Blk[i, j];
}
}
// 重なってない場合は描画する
Render();
return false;
}
void EraseBlock()
{
int comp = 0;
int i, j, k;
while(true)
{
for (j = 0; j < 20; j++)
{
// 揃っているかチェックをリセット
comp = 1;
// 行の中に
for (i = 3; i < 13; i++)
{
// 0が1つでもあればcomp=0(揃っていない)
if(Stage[i,j] == 0)
{
comp = 0;
}
}
// 揃っていれば抜ける
if(comp == 1)
{
break;
}
}
// 揃っていなければ終了
if(comp == 0)
{
break;
}
// 1行を0にする
for (i = 3; i < 3; i++)
{
if(Stage[i,j] == 0)
{
comp = 0;
}
}
// 上の行を下におろす
for (k = j; k > 0; k--)
{
for (i = 3; i < 13; i++)
{
Stage[i, k] = Stage[i, k - 1];
}
}
}
}
}
実行結果

これで横1行揃ったらブロックが消えるようになりました!次回以降はスコアの表示やゲームオーバーの追加をやっていきます。
スコアの表示
3DTextを3つ作って、ScoreText,LineText,LevelTextと名前を付けます。

Position | Rotation | |
LevelText | 25,0,0 | 0,180,0 |
ScoreText | 25,-2,0 | 0,180,0 |
LineText | 25,-4,0 | 0,180,0 |
TextMeshの設定を以下のようにすると文字がきれいになります。

それではスコアとラインを表示するスクリプトを書きましょう。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CGameManager : MonoBehaviour
{
const int BLK_WITH = 16;
const int BLK_HEIGHT = 23;
const int FIELD_WITH = 14;
const int FIELD_HEIGHT = 21;
const int ONE_BLK_NUM = 4;
public GameObject BlkObj;
int BlkNo;
// フィールドの状態
public int[,] Field = new int[BLK_WITH, BLK_HEIGHT];
// フィールドの状態を保存(ブロックの固定)
public int[,] Stage = new int[BLK_WITH, BLK_HEIGHT];
// ブロックを表現する配列
public int[,] Blk = new int[ONE_BLK_NUM, ONE_BLK_NUM];
// ブロックを一時的に保存する配列(回転の時に使う)
int[,] Blktmp = new int[ONE_BLK_NUM, ONE_BLK_NUM];
public float Delta;
// 間隔
float Span = 0.5f;
// ブロックの座標(初期位置)
const int StartX = 5;
int X = StartX, Y = 0;
public int Score = 0;
public int Lines = 0;
public int Level = 0;
public int Lines2 = 0;
// 文字の格納
public GameObject SC;
public GameObject LN;
public GameObject LV;
void Start()
{
// 壁の設定
for(int i = 0; i < BLK_WITH; ++i)
{
for(int j = 0; j < BLK_HEIGHT; ++j)
{
// 壁にする部分は9
if(i == 2 || i == 13 || j == 20)
{
Stage[i, j] = 9;
}
else
{
Stage[i, j] = 0;
}
}
}
// ステージデータを描く
for (int i = 2; i < FIELD_WITH; ++i)
{
for (int j = 0; j < FIELD_HEIGHT; ++j)
{
Field[i, j] = Stage[i, j];
}
}
// 左上から右下にブロックを生成する)
for (int i = 2; i < FIELD_WITH; ++i)
{
for(int j = 0; j < FIELD_HEIGHT; ++j)
{
Instantiate(BlkObj, new Vector3(i * 1.0f, j * -1.0f, 0f), Quaternion.identity);
}
}
// ブロックの作成
MakeBlock();
// フィールドを描画
Render();
}
float TotalTime = 0.0f;
void Update()
{
Delta += Time.deltaTime;
if (Delta > Span)
{
// 下に移動できたなら
if (CheckBlock(X, Y + 1))
{
// 下に移動する
MoveBlock(X, Y + 1);
}
else // 下に移動できなかったら
{
// ブロックをロックする
LockBlock();
// 1行揃ったら消す
EraseBlock();
// 新しいブロックを生成する
MakeBlock();
// 描画しなおす
Render();
}
// 経過時間の初期化
Delta = 0;
}
TotalTime += Time.deltaTime;
// 右へ移動する
if (Input.GetKey(KeyCode.RightArrow) && CheckBlock(X - 1, Y) && 0.25f < TotalTime)
{
MoveBlock(X - 1, Y);
TotalTime = 0.0f;
}
// 左へ移動する
if (Input.GetKey(KeyCode.LeftArrow) && CheckBlock(X + 1, Y) && 0.25f < TotalTime)
{
MoveBlock(X + 1, Y);
TotalTime = 0.0f;
}
// 回転させる
if(Input.GetKeyDown(KeyCode.Space))
{
TrunBlock();
}
}
void MakeBlock()
{
// ランダムに0~6までの乱数を発生
BlkNo = Random.Range(0, 7);
switch(BlkNo)
{
case 0:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,0,0},
{0,1,0,0},
};
break;
case 1:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,0,1,0},
{0,0,1,0},
};
break;
case 2:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,1,0},
{0,0,0,0},
};
break;
case 3:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{1,1,1,0},
{0,0,0,0},
};
break;
case 4:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{0,1,1,0},
{0,0,1,0},
};
break;
case 5:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,0,1,0},
{0,1,1,0},
{0,1,0,0},
};
break;
case 6:
Blk = new int[4, 4]
{
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
};
break;
default:
break;
}
// ブロックを初期位置にする
X = StartX;
Y = 0;
// フィールドにブロックを加える
for(int i = 0; i < 4; ++i)
{
for(int j = 0; j < 4; ++j)
{
Field[i + StartX, j] = Stage[i + StartX, j] + Blk[i, j];
}
}
}
void MoveBlock(int x, int y)
{
// ブロックを一旦消す
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] -= Blk[i, j];
}
}
X = x;
Y = y;
// ブロックを追加
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] += Blk[i, j];
}
}
}
bool CheckBlock(int x, int y)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
// ブロックがあって
if(Blk[i, j] != 0)
{
// 移動予定地もブロックなら
if(Stage[x+i,y+j] != 0)
{
// 移動できない
return false;
}
}
}
}
// 重なってないので移動できる
return true;
}
void LockBlock()
{
for (int i = 2; i < FIELD_WITH; i++)
{
for (int j = 0; j < FIELD_HEIGHT; j++)
{
Stage[i, j] = Field[i, j];
}
}
}
void Render()
{
// ステージを描く
for (int i = 2; i < FIELD_WITH; i++)
{
for (int j = 0; j < FIELD_HEIGHT; j++)
{
Field[i, j] = Stage[i, j];
}
}
// ブロックを追加する
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Field[i + X, j + Y] = Stage[i + X, i + Y] + Blk[i, j];
}
}
}
bool TrunBlock()
{
// ブロックを一時的に保存
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blktmp[i, j] = Blk[i, j];
}
}
// ブロックを回転させる(本来の座標XYをひっくり返して3を引けば回転する)
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blk[i, j] = Blktmp[3 - j, i];
}
}
// 回転先に重なりがあれば元に戻す
if(!CheckBlock(X,Y))
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blk[i, j] = Blktmp[i, j];
}
}
return true;
}
// 重なったブロックを消す
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Field[i + X, j + Y] -= Blktmp[i, j];
Field[i + X, j + Y] -= Blk[i, j];
}
}
// 重なってない場合は描画する
Render();
return false;
}
void EraseBlock()
{
int comp = 0;
int i, j, k;
while(true)
{
for (j = 0; j < 20; j++)
{
// 揃っているかチェックをリセット
comp = 1;
// 行の中に
for (i = 3; i < 13; i++)
{
// 0が1つでもあればcomp=0(揃っていない)
if(Stage[i,j] == 0)
{
comp = 0;
}
}
// 揃っていれば抜ける
if(comp == 1)
{
break;
}
}
// 揃っていなければ終了
if(comp == 0)
{
break;
}
// ラインの数の加算
Lines++;
// 消したライン数の合計
Lines2++;
// 得点の加算
switch(Lines)
{
case 1:
Score += 100;
Lines = 0;
break;
case 2:
Score += 200;
Lines = 0;
break;
case 3:
Score += 300;
Lines = 0;
break;
case 4:
Score += 2000;
Lines = 0;
break;
}
// スコアの表示
SC.GetComponent<TextMesh>().text = "Score: " + Score.ToString();
// 消したラインの表示
LN.GetComponent<TextMesh>().text = "Total Line: " + Lines2.ToString();
// 1行を0にする
for (i = 3; i < 3; i++)
{
if(Stage[i,j] == 0)
{
comp = 0;
}
}
// 上の行を下におろす
for (k = j; k > 0; k--)
{
for (i = 3; i < 13; i++)
{
Stage[i, k] = Stage[i, k - 1];
}
}
}
}
}
GameObject型で宣言したSC,LN,LVは以下のようにアタッチしておきましょう。

ここまででスコアとラインの表示ができるようになりました。
ゲームオーバーのチェックとリトライ
GameOverFlagを追加して、MakeBlock内のステージを加える処理の中にゲームオーバーかどうか判定する処理を追加します。そして、ゲームオーバーの時はUpdateでテキストを変更する処理とリトライかやめるのを選択する処理を追加します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CGameManager : MonoBehaviour
{
const int BLK_WITH = 16;
const int BLK_HEIGHT = 23;
const int FIELD_WITH = 14;
const int FIELD_HEIGHT = 21;
const int ONE_BLK_NUM = 4;
public GameObject BlkObj;
int BlkNo;
bool GameOverFlag = false;
// フィールドの状態
public int[,] Field = new int[BLK_WITH, BLK_HEIGHT];
// フィールドの状態を保存(ブロックの固定)
public int[,] Stage = new int[BLK_WITH, BLK_HEIGHT];
// ブロックを表現する配列
public int[,] Blk = new int[ONE_BLK_NUM, ONE_BLK_NUM];
// ブロックを一時的に保存する配列(回転の時に使う)
int[,] Blktmp = new int[ONE_BLK_NUM, ONE_BLK_NUM];
public float Delta;
// 間隔
float Span = 0.5f;
// ブロックの座標(初期位置)
const int StartX = 5;
int X = StartX, Y = 0;
public int Score = 0;
public int Lines = 0;
public int Level = 0;
public int Lines2 = 0;
// 文字の格納
public GameObject SC;
public GameObject LN;
public GameObject LV;
void Start()
{
// 壁の設定
for(int i = 0; i < BLK_WITH; ++i)
{
for(int j = 0; j < BLK_HEIGHT; ++j)
{
// 壁にする部分は9
if(i == 2 || i == 13 || j == 20)
{
Stage[i, j] = 9;
}
else
{
Stage[i, j] = 0;
}
}
}
// ステージデータを描く
for (int i = 2; i < FIELD_WITH; ++i)
{
for (int j = 0; j < FIELD_HEIGHT; ++j)
{
Field[i, j] = Stage[i, j];
}
}
// 左上から右下にブロックを生成する)
for (int i = 2; i < FIELD_WITH; ++i)
{
for(int j = 0; j < FIELD_HEIGHT; ++j)
{
Instantiate(BlkObj, new Vector3(i * 1.0f, j * -1.0f, 0f), Quaternion.identity);
}
}
// ブロックの作成
MakeBlock();
// フィールドを描画
Render();
}
float TotalTime = 0.0f;
void Update()
{
if(GameOverFlag)
{
LV.GetComponent<TextMesh>().text = "Game Over";
SC.GetComponent<TextMesh>().text = "Continue:Press[R]";
LN.GetComponent<TextMesh>().text = "EXIT:Press[Q]";
// Qキーを推したらやめる
if(Input.GetKeyDown(KeyCode.Q))
{
Application.Quit();
}
// Rキーを推したら再開する
if(Input.GetKeyDown(KeyCode.R))
{
Delta = 0;
Level = 0;
Score = 0;
Lines = 0;
Lines2 = 0;
GameOverFlag = false;
// ステージデータを描く
for (int i = 3; i < 13; i++)
{
for (int j = 0; j < 20; j++)
{
Stage[i, j] = 0;
Field[i, j] = Stage[i, j];
}
}
Render();
}
return;
}
Delta += Time.deltaTime;
if (Delta > Span)
{
// 下に移動できたなら
if (CheckBlock(X, Y + 1))
{
// 下に移動する
MoveBlock(X, Y + 1);
}
else // 下に移動できなかったら
{
// ブロックをロックする
LockBlock();
// 1行揃ったら消す
EraseBlock();
// 新しいブロックを生成する
MakeBlock();
// 描画しなおす
Render();
}
// 経過時間の初期化
Delta = 0;
}
TotalTime += Time.deltaTime;
// 右へ移動する
if (Input.GetKey(KeyCode.RightArrow) && CheckBlock(X - 1, Y) && 0.25f < TotalTime)
{
MoveBlock(X - 1, Y);
TotalTime = 0.0f;
}
// 左へ移動する
if (Input.GetKey(KeyCode.LeftArrow) && CheckBlock(X + 1, Y) && 0.25f < TotalTime)
{
MoveBlock(X + 1, Y);
TotalTime = 0.0f;
}
// 回転させる
if(Input.GetKeyDown(KeyCode.Space))
{
TrunBlock();
}
}
void MakeBlock()
{
// ランダムに0~6までの乱数を発生
BlkNo = Random.Range(0, 7);
switch(BlkNo)
{
case 0:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,0,0},
{0,1,0,0},
};
break;
case 1:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,0,1,0},
{0,0,1,0},
};
break;
case 2:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,1,0},
{0,0,0,0},
};
break;
case 3:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{1,1,1,0},
{0,0,0,0},
};
break;
case 4:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{0,1,1,0},
{0,0,1,0},
};
break;
case 5:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,0,1,0},
{0,1,1,0},
{0,1,0,0},
};
break;
case 6:
Blk = new int[4, 4]
{
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
};
break;
default:
break;
}
// ブロックを初期位置にする
X = StartX;
Y = 0;
// フィールドにブロックを加える
for(int i = 0; i < 4; ++i)
{
for(int j = 0; j < 4; ++j)
{
Field[i + StartX, j] = Stage[i + StartX, j] + Blk[i, j];
// ゲームオーバーかチェックする
// 移動先にブロックがあればフラグを立てる
if (Field[i + X, j + Y + 1] > 0)
{
GameOverFlag = true;
}
}
}
}
void MoveBlock(int x, int y)
{
// ブロックを一旦消す
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] -= Blk[i, j];
}
}
X = x;
Y = y;
// ブロックを追加
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] += Blk[i, j];
}
}
}
bool CheckBlock(int x, int y)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
// ブロックがあって
if(Blk[i, j] != 0)
{
// 移動予定地もブロックなら
if(Stage[x+i,y+j] != 0)
{
// 移動できない
return false;
}
}
}
}
// 重なってないので移動できる
return true;
}
void LockBlock()
{
for (int i = 2; i < FIELD_WITH; i++)
{
for (int j = 0; j < FIELD_HEIGHT; j++)
{
Stage[i, j] = Field[i, j];
}
}
}
void Render()
{
// ステージを描く
for (int i = 2; i < FIELD_WITH; i++)
{
for (int j = 0; j < FIELD_HEIGHT; j++)
{
Field[i, j] = Stage[i, j];
}
}
// ブロックを追加する
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Field[i + X, j + Y] = Stage[i + X, i + Y] + Blk[i, j];
}
}
}
bool TrunBlock()
{
// ブロックを一時的に保存
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blktmp[i, j] = Blk[i, j];
}
}
// ブロックを回転させる(本来の座標XYをひっくり返して3を引けば回転する)
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blk[i, j] = Blktmp[3 - j, i];
}
}
// 回転先に重なりがあれば元に戻す
if(!CheckBlock(X,Y))
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blk[i, j] = Blktmp[i, j];
}
}
return true;
}
// 重なったブロックを消す
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Field[i + X, j + Y] -= Blktmp[i, j];
Field[i + X, j + Y] -= Blk[i, j];
}
}
// 重なってない場合は描画する
Render();
return false;
}
void EraseBlock()
{
int comp = 0;
int i, j, k;
while(true)
{
for (j = 0; j < 20; j++)
{
// 揃っているかチェックをリセット
comp = 1;
// 行の中に
for (i = 3; i < 13; i++)
{
// 0が1つでもあればcomp=0(揃っていない)
if(Stage[i,j] == 0)
{
comp = 0;
}
}
// 揃っていれば抜ける
if(comp == 1)
{
break;
}
}
// 揃っていなければ終了
if(comp == 0)
{
break;
}
// ラインの数の加算
Lines++;
// 消したライン数の合計
Lines2++;
// 得点の加算
switch(Lines)
{
case 1:
Score += 100;
Lines = 0;
break;
case 2:
Score += 200;
Lines = 0;
break;
case 3:
Score += 300;
Lines = 0;
break;
case 4:
Score += 2000;
Lines = 0;
break;
}
// スコアの表示
SC.GetComponent<TextMesh>().text = "Score: " + Score.ToString();
// 消したラインの表示
LN.GetComponent<TextMesh>().text = "Total Line: " + Lines2.ToString();
// 1行を0にする
for (i = 3; i < 3; i++)
{
if(Stage[i,j] == 0)
{
comp = 0;
}
}
// 上の行を下におろす
for (k = j; k > 0; k--)
{
for (i = 3; i < 13; i++)
{
Stage[i, k] = Stage[i, k - 1];
}
}
}
}
}
レベルの調整
得点が高くなっていくほどレベルが上がっていく仕組みにします。ついでに下キーを押すと早く落ちる処理も実装します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CGameManager : MonoBehaviour
{
const int BLK_WITH = 16;
const int BLK_HEIGHT = 23;
const int FIELD_WITH = 14;
const int FIELD_HEIGHT = 21;
const int ONE_BLK_NUM = 4;
public GameObject BlkObj;
int BlkNo;
bool GameOverFlag = false;
// フィールドの状態
public int[,] Field = new int[BLK_WITH, BLK_HEIGHT];
// フィールドの状態を保存(ブロックの固定)
public int[,] Stage = new int[BLK_WITH, BLK_HEIGHT];
// ブロックを表現する配列
public int[,] Blk = new int[ONE_BLK_NUM, ONE_BLK_NUM];
// ブロックを一時的に保存する配列(回転の時に使う)
int[,] Blktmp = new int[ONE_BLK_NUM, ONE_BLK_NUM];
public float Delta;
// 間隔
float Span = 0.5f;
// ブロックの座標(初期位置)
const int StartX = 5;
int X = StartX, Y = 0;
public int Score = 0;
public int Lines = 0;
public int Level = 0;
public int Lines2 = 0;
// 文字の格納
public GameObject SC;
public GameObject LN;
public GameObject LV;
void Start()
{
// 壁の設定
for(int i = 0; i < BLK_WITH; ++i)
{
for(int j = 0; j < BLK_HEIGHT; ++j)
{
// 壁にする部分は9
if(i == 2 || i == 13 || j == 20)
{
Stage[i, j] = 9;
}
else
{
Stage[i, j] = 0;
}
}
}
// ステージデータを描く
for (int i = 2; i < FIELD_WITH; ++i)
{
for (int j = 0; j < FIELD_HEIGHT; ++j)
{
Field[i, j] = Stage[i, j];
}
}
// 左上から右下にブロックを生成する)
for (int i = 2; i < FIELD_WITH; ++i)
{
for(int j = 0; j < FIELD_HEIGHT; ++j)
{
Instantiate(BlkObj, new Vector3(i * 1.0f, j * -1.0f, 0f), Quaternion.identity);
}
}
// ブロックの作成
MakeBlock();
// フィールドを描画
Render();
}
float TotalTime = 0.0f;
void Update()
{
if(GameOverFlag)
{
LV.GetComponent<TextMesh>().text = "Game Over";
SC.GetComponent<TextMesh>().text = "Continue:Press[R]";
LN.GetComponent<TextMesh>().text = "EXIT:Press[Q]";
// Qキーを推したらやめる
if(Input.GetKeyDown(KeyCode.Q))
{
Application.Quit();
}
// Rキーを推したら再開する
if(Input.GetKeyDown(KeyCode.R))
{
Delta = 0;
Level = 0;
Score = 0;
Lines = 0;
Lines2 = 0;
GameOverFlag = false;
// ステージデータを描く
for (int i = 3; i < 13; i++)
{
for (int j = 0; j < 20; j++)
{
Stage[i, j] = 0;
Field[i, j] = Stage[i, j];
}
}
Render();
}
return;
}
// レベルの調整
if(Score >= 1000)
{
Level = 5;
Span = 0.075f;
}
else if(Score >= 400)
{
Level = 4;
Span = 0.15f;
}
else if (Score >= 300)
{
Level = 3;
Span = 0.3f;
}
else if (Score >= 200)
{
Level = 2;
Span = 0.4f;
}
else if (Score >= 100)
{
Level = 1;
Span = 0.6f;
}
Delta += Time.deltaTime;
if (Delta > Span)
{
// 下に移動できたなら
if (CheckBlock(X, Y + 1))
{
// 下に移動する
MoveBlock(X, Y + 1);
}
else // 下に移動できなかったら
{
// ブロックをロックする
LockBlock();
// 1行揃ったら消す
EraseBlock();
// 新しいブロックを生成する
MakeBlock();
// 描画しなおす
Render();
}
// 経過時間の初期化
Delta = 0;
}
TotalTime += Time.deltaTime;
// 右へ移動する
if (Input.GetKey(KeyCode.RightArrow) && CheckBlock(X - 1, Y) && 0.25f < TotalTime)
{
MoveBlock(X - 1, Y);
TotalTime = 0.0f;
}
// 左へ移動する
if (Input.GetKey(KeyCode.LeftArrow) && CheckBlock(X + 1, Y) && 0.25f < TotalTime)
{
MoveBlock(X + 1, Y);
TotalTime = 0.0f;
}
// 下へ移動する
if (Input.GetKey(KeyCode.DownArrow) && CheckBlock(X, Y+1) && 0.25f < TotalTime)
{
MoveBlock(X, Y+1);
TotalTime = 0.0f;
}
// 回転させる
if (Input.GetKeyDown(KeyCode.Space))
{
TrunBlock();
}
}
void MakeBlock()
{
// ランダムに0~6までの乱数を発生
BlkNo = Random.Range(0, 7);
switch(BlkNo)
{
case 0:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,0,0},
{0,1,0,0},
};
break;
case 1:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,0,1,0},
{0,0,1,0},
};
break;
case 2:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,1,0},
{0,1,1,0},
{0,0,0,0},
};
break;
case 3:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{1,1,1,0},
{0,0,0,0},
};
break;
case 4:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,1,0,0},
{0,1,1,0},
{0,0,1,0},
};
break;
case 5:
Blk = new int[4, 4]
{
{0,0,0,0},
{0,0,1,0},
{0,1,1,0},
{0,1,0,0},
};
break;
case 6:
Blk = new int[4, 4]
{
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
};
break;
default:
break;
}
// ブロックを初期位置にする
X = StartX;
Y = 0;
// フィールドにブロックを加える
for(int i = 0; i < 4; ++i)
{
for(int j = 0; j < 4; ++j)
{
Field[i + StartX, j] = Stage[i + StartX, j] + Blk[i, j];
// ゲームオーバーかチェックする
// 移動先にブロックがあればフラグを立てる
if (Field[i + X, j + Y + 1] > 0)
{
GameOverFlag = true;
}
}
}
}
void MoveBlock(int x, int y)
{
// ブロックを一旦消す
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] -= Blk[i, j];
}
}
X = x;
Y = y;
// ブロックを追加
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; j++)
{
Field[X + i, Y + j] += Blk[i, j];
}
}
}
bool CheckBlock(int x, int y)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
// ブロックがあって
if(Blk[i, j] != 0)
{
// 移動予定地もブロックなら
if(Stage[x+i,y+j] != 0)
{
// 移動できない
return false;
}
}
}
}
// 重なってないので移動できる
return true;
}
void LockBlock()
{
for (int i = 2; i < FIELD_WITH; i++)
{
for (int j = 0; j < FIELD_HEIGHT; j++)
{
Stage[i, j] = Field[i, j];
}
}
}
void Render()
{
// ステージを描く
for (int i = 2; i < FIELD_WITH; i++)
{
for (int j = 0; j < FIELD_HEIGHT; j++)
{
Field[i, j] = Stage[i, j];
}
}
// ブロックを追加する
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Field[i + X, j + Y] = Stage[i + X, i + Y] + Blk[i, j];
}
}
}
bool TrunBlock()
{
// ブロックを一時的に保存
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blktmp[i, j] = Blk[i, j];
}
}
// ブロックを回転させる(本来の座標XYをひっくり返して3を引けば回転する)
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blk[i, j] = Blktmp[3 - j, i];
}
}
// 回転先に重なりがあれば元に戻す
if(!CheckBlock(X,Y))
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Blk[i, j] = Blktmp[i, j];
}
}
return true;
}
// 重なったブロックを消す
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Field[i + X, j + Y] -= Blktmp[i, j];
Field[i + X, j + Y] -= Blk[i, j];
}
}
// 重なってない場合は描画する
Render();
return false;
}
void EraseBlock()
{
int comp = 0;
int i, j, k;
while(true)
{
for (j = 0; j < 20; j++)
{
// 揃っているかチェックをリセット
comp = 1;
// 行の中に
for (i = 3; i < 13; i++)
{
// 0が1つでもあればcomp=0(揃っていない)
if(Stage[i,j] == 0)
{
comp = 0;
}
}
// 揃っていれば抜ける
if(comp == 1)
{
break;
}
}
// 揃っていなければ終了
if(comp == 0)
{
break;
}
// ラインの数の加算
Lines++;
// 消したライン数の合計
Lines2++;
// 得点の加算
switch(Lines)
{
case 1:
Score += 100;
Lines = 0;
break;
case 2:
Score += 200;
Lines = 0;
break;
case 3:
Score += 300;
Lines = 0;
break;
case 4:
Score += 2000;
Lines = 0;
break;
}
// スコアの表示
SC.GetComponent<TextMesh>().text = "Score: " + Score.ToString();
// 消したラインの表示
LN.GetComponent<TextMesh>().text = "Total Line: " + Lines2.ToString();
// 1行を0にする
for (i = 3; i < 3; i++)
{
if(Stage[i,j] == 0)
{
comp = 0;
}
}
// 上の行を下におろす
for (k = j; k > 0; k--)
{
for (i = 3; i < 13; i++)
{
Stage[i, k] = Stage[i, k - 1];
}
}
}
}
}
実行結果

これですべての機能は追加されました!お疲れ様でした。
アイコンは夏代けい@ついったーさんからお借りしました。ありがとうございます!