VBAテトリス 詳細編 テトリミノを操作する
どうもこんにちは。
エクセルのマクロでこんな感じのテトリスを作りました。
programminghajimetemita.hatenablog.com
このテトリスのコードをどうやって作ったか解説していきます。
今回は「テトリミノを操作する」処理についてです。
- 1.テトリミノを操作するとは?
- 2.テトリミノの操作-任意の位置に移動させる
- 3.テトリミノの移動を処理するコード
- 4.テトリミノの操作-テトリミノを回転させる
- 5.テトリミノの回転を処理するコード
- 6.おわりに
1.テトリミノを操作するとは?
具体的なコードの話に入る前に、
「そもそもテトリミノを操作するって何のことを言っているのか」
についてお話しします。
操作するという言葉でイメージがついているかもしれませんが、
「テトリミノを操作する」とは、テトリミノの落下中に、
・テトリミノを左に移動させる
・テトリミノを右に移動させる
・テトリミノを下に移動させる
・テトリミノを回転させる
の4つの動作をユーザーのボタン操作で行うことを指すと捉えてください。
ざっくり言えば、テトリミノの移動と回転ですね。
では、これらの処理を行うためにどんなコードを組む必要があるか、
次の章から解説していきたいと思います。
2.テトリミノの操作-任意の位置に移動させる
まずはテトリミノの移動の処理から解説していきたいと思います。
コードの内容に入る前にテトリスの挙動をおさらいします。
テトリスでは、テトリミノの落下中に左ボタンを押すとテトリミノが左に移動、
右ボタンを押すとテトリミノが右に移動、
下ボタンを押すとテトリミノが素早く落下すると思います。
当たり前の挙動ですが、これを考えることでどういう処理をさせればよいかが見えてきますので、
少し冗長かもしれませんがお付き合いください。
ということで挙動を踏まえると必要な処理は以下になります。
・左ボタンを押したらテトリミノを左に1列移動させる
・右ボタンを押したらテトリミノを右に1列移動させる
・下ボタンを押したらテトリミノを下により素早く移動させる(=下に移動する行数を増やす)
では、この処理を実現するコードの内容に入っていきましょう。
3.テトリミノの移動を処理するコード
まずは簡単な部分からコードの内容に触れていきます。
左に移動する処理から考えてみましょう。
上述の通り、やりたい処理は「左ボタンを押したら左に1列移動」です。
これはIfによる条件分岐を使えば実現できそうです。
If (左ボタンが押された) Then
c = c - 1
End If
これまでに解説してきた通り、テトリミノの位置は変数(r, c)によって制御されています。
ここで、cの値は何も変更が加えられていなければ、初期位置の列になっています。
ですので、左方向に1列移動させたい場合はcに"c - 1(現在地の列の左隣列)"を代入してやればよいというわけです。
他の場合も同様で、移動させたい方向に変数の値を調整してやればOKです。
右移動であればcに"c + 1"を代入、
下移動であればrに"r + X(Xはどんな数字でも可)"を代入してやればよいです。
(下方向の移動は行方向の移動になりますから、調整する変数はrになります。
また、"素早く落下"なのでXの数字を大きくすればするほど落下スピードは増します。
これは自分の匙加減によると思いますので挙動を見ながら好きな数字を入れましょう。)
あとは上記のコードの中でコード化ができていなかった、
(左ボタンが押された)をどのようなコードにするかです。
これはVBAの枠外の力を借りて行います。
具体的にはGetAsyncKeyStateという関数を利用します。
この関数を利用すると、
"キーボード上のボタンの現在の状態(押されているかどうか)"
を取得することができます。
現在の状態は数字で表され、
ボタンが押されていないのであれば"0"が返されます。
(押された状態は複数の種類があるのですが、細かいので割愛します)
なので、この関数が実行されたときに、ボタンが押されていなければ、
"0"という数値が返ってくるということになります。
ここで、今処理に組み込みたいのは(ボタンが押された)という条件です。
上記の通り、押されていない:関数=0 ですので、
押された:関数=0でない、つまり、関数<>0という式にすればよいですね。
ということで、最終的なコードは以下のようになります。
If GetAsyncKeyState(vbKeyLeft) <> 0 Then
c = c - 1
End If
上記は左ボタンの例ですが、別のボタンで同様のことをやりたいときは、
GetAsyncKeyState関数のカッコ書きの部分を書き換えればOKです。
右ボタンなら(vbKeyRight)、下ボタンなら(vbKeyDown)といった具合です。
なお、この関数は最初にVBAの枠外の力といった通り、VBAに規定されている関数ではないので、
利用するにあたっては事前準備が必要になります。
具体的には、コードの一番上の行に以下の記述を行う必要があります。
この関数を利用するモジュールそれぞれで記述が必要になりますのでご留意ください。
#If VBA7 Then
'32bit PC
Private Declare Function GetAsyncKeyState Lib "user32.dll" (ByVal vKey As Long) As Long
#Else
'64bit PC
Private Declare PtrSafe Function GetAsyncKeyState Lib "user32.dll" (ByVal vKey As LongPtr) As Long
#End If
以上でテトリミノの移動についての解説を終わります。
4.テトリミノの操作-テトリミノを回転させる
続いてテトリミノの回転の処理です。
ここでもまずはテトリスの挙動を考えます。
テトリスでは回転に対応するボタンを押すと、
表示されているテトリミノが90度(テトリミノの種類によっては180度)回転します。
ここで"回転"についてもう少し深堀して考えてみます。
ゲーム画面上、ボタンを押すと表示されているテトリミノがぐるりと回ったように見えますが、
これはボタンを押すと、現在表示されているテトリミノが消えて、
90度回転した形のテトリミノが表示される、とも考えることができます。
これで、どのような処理をさせればよいかがはっきりしましたね。
以下が回転を再現するのに必要な処理です。
・任意のボタンを押したら90度(あるいは180度)回転した形のテトリミノを表示する
では具体的なコードの内容に入っていきましょう。
5.テトリミノの回転を処理するコード
回転に必要な処理は、上述の通り、
90度回転した形のテトリミノを表示すること、です。
また、常に、ボタンを押す前に表示されているテトリミノの形から、
90度回転した形にする必要があります。
これを実現するために以下2つの処理を準備しました。
・回転後の図形ごとに番号を割り振る
・ボタンを押すと上記の番号が切り替わる
順に詳しく解説してきます。
・回転後の図形ごとに番号を割り振る
これはテトリミノの種類ごとに番号を割り振ったのと似た発想です。
取り得る形を書き出して、それぞれに番号を割り振りました。
具体的には以下のようなイメージです。
("block"がテトリミノの種類を示す番号、"rotate_num"が回転後の図形の種類を示す番号になります)
あとは、blockやrotate_numに対応する形を呼び出せるようにしておけばよい、というわけです。
具体的なコードは以下の内容になります。
-テトリミノのセル範囲を設定するマクロー
※block1はRange型のオブジェクト変数
Sub setblock1(r As Integer, c As Integer, rotate_num As Integer)
Select Case rotate_num
Case 0
Set block1 = Union(Cells(r, c), Range(Cells(r + 1, c), Cells(r + 1, c + 2)))
Case 1
Set block1 = Union(Range(Cells(r, c), Cells(r, c + 1)), Range(Cells(r + 1, c), Cells(r + 2, c)))
Case 2
Set block1 = Union(Range(Cells(r, c), Cells(r, c + 2)), Cells(r + 1, c + 2))
Case 3
Set block1 = Union(Cells(r + 2, c), Range(Cells(r, c + 1), Cells(r + 2, c + 1)))
End Select
End Sub
・・・以下テトリミノ種類ごとに同様の記述を行う
ーテトリミノを表示するマクロー
Sub createblock(block As Integer, r As Integer, c As Integer, rotate_num As Integer)
Select Case block
Case 1
Call setblock1(r, c, rotate_num)
With block1
.Interior.ColorIndex = 1
.Borders.LineStyle = xlDouble
.Borders.ColorIndex = 2
End With
・・・以下テトリミノの種類ごとに同様の記述を行う
コードのポイントについて解説します。
まず、テトリミノのセル範囲を設定するマクロについてです。
上記で示しているコードは先ほどお見せしたイメージ図の一行目、
L字型ブロックのセル範囲を設定するコードになります。
回転後の図形に割り振った番号ごとに場合分けをし、
それぞれの図形に対応するセル範囲を設定しています。
このマクロを実行する際には、r, c, rotate_numの3つの変数が連携されます。
変数r, cによってどの座標を設定するか、変数rotate_numによってどの図形を表示するかが決定されるということです。
次にテトリミノを表示するマクロについてです。
こちらは以前解説したマクロになりますが、以前解説した時には図形の回転を無視した内容でしたので、
改めてコードを紹介しました。
以前紹介したコードと異なる点は、引数にrotate_numが追加されていること、
それぞれのケースにおいてテトリミノのセル範囲を設定するマクロを呼び出していること、
の2点です。
どういう処理をしているかというと、
まずは変数blockによってどの種類のテトリミノを呼び出すかを決定しています。
次に、変数rotate_numによってテトリミノのうちのどの図形を呼び出すかが決まり、
変数r, cによって呼び出す座標が決まります。
呼び出す形、座標が決まれば、あとはその範囲に書式を付加して処理終了となります。
まとめると、rotate_numという要素を加えることで回転状態に応じた適切な図形が表示されるようになったということです。
・ボタンを押すと回転状態を示す番号(rotate_num)が切り替わる
ここまでで回転の状態に合わせた適切な図形が表示されるようになりました。
あとは回転の状態をボタン押下によって制御できればよいですね。
「ボタンを押す」の部分については、
テトリミノの移動の時と同様にGetAsyncKeyState関数を利用します。
「番号が切り替わる」の部分については、
ボタンを押す度に、0→1→2→3→0・・・と切り替わっていけばよいので、
ボタンが押されたときに、
・rotate_numが0、ならば、rotate_numは1になる
・rotate_numが1、ならば、rotate_numは2になる
・rotate_numが2、ならば、rotate_numは3になる
・rotate_numが3、ならば、rotate_numは0になる
と処理させればよいですね。
ということでコードは以下のようになります。
If GetAsyncKeyState(vbKeyControl) <> 0 Then
If rotate_num = 0 Then
rotate_num = 1
ElseIf rotate_num = 1 Then
rotate_num = 2
ElseIf rotate_num = 2 Then
rotate_num = 3
Else
rotate_num = 0
End If
End If
番号の切り替え部分は以下のようなコードでもいいかもです。
If rotate_num <> 3 Then
rotate_num = rotate_num + 1
Else
rotate_num = 0
End If
紹介していて冗長に感じたので改善案もついでに紹介してみました。
こんな感じでイケてないコードが多々あるかと思いますので、
やりたい処理をつかんでいただけたら、
ここで紹介しているコードを使わず、
よりシュッと表現できるコードを使ってください。
以上でテトリミノを回転させる処理のコード解説を終わります。
6.おわりに
今回はテトリミノの操作について解説しました。
この処理を組み込むことで移動や回転が行えるようになるので、
興味があればぜひご自分のPCで試してみてください。
実は今回紹介した内容は完全ではありません。
なぜなら、移動や回転に何も制限をかけていないので、
壁や周囲のブロックを無視して移動・回転ができてしまうからです。
この状態ではテトリスが成り立ちませんね。
ではどうすれば制限をかけることができるのか?
次回はそのあたりを中心に解説していきたいと思います。
ではまた。