Excelで使えるVBAはプログラミングの基礎を学びやすい簡単な言語です。
Excelのワークシート上で動作するゲームを作って、プログラミングの仕組みを理解してみましょう。
初心者でもわかるように細かく解説していきます。
目次
スネークゲームを自作しよう
スネークゲームとはランダムで出現する餌を回収して蛇の体を伸ばしていくゲームです。
自分の体や壁にぶつかるとゲームオーバーになります。
ヘビゲームって言われることの方が多いかもしれないです。
上の画像が僕が作成したスネークゲームです。
難易度選択やランキング機能が付いていますが、まずはゲームとしてプレイできることを目標に組み立てていきます。
ファイルの保存
プログラミングを始める前に、ファイルを保存しておきましょう。
ファイルを保存する際に、ファイルの種類をExcel マクロ有効ブックに変更してください。
通常の拡張子のまま保存するとVBAのデータが吹っ飛びます。
注意しましょう。
外部設計
まずはゲームのプレイ画面を作成します。
ワークシートのセルを活用するので、すべてのセルを正方形にしてください。
15×15になるように罫線を引きます。
C3からQ17までの範囲で作成してください。
セル番地がずれるとコードを書き換える必要が出てきて混乱が生じます。
方向を入力するコマンドボタンを4つ作成します。
このコマンドボタンをクリックすると蛇(青いブロック)を操作できるようにします。
コマンドボタンは開発タブの挿入から選択できます。
開発タブが表示されていない場合はオプションのリボンのユーザー設定から開発にチェックを入れてください。
いちいちマウスでクリックするのは面倒なので、キーボードでも操作できるように後で設定します。
スタートボタンがないとゲームが始まりませんね。
方向ボタンの下にでも同様の手順で作っておきましょう。
コマンドボタンの文字はプロパティから変更できます。
デザインモードに変更してから右クリックで表示できます。
「Caption」が実際に表示される文字列です。
プログラムに記述する際の名称は(オブジェクト名)で指定します。
「Caption」とは異なる点に注意しましょう。
プログラミング
次に、ゲームを動かすためのコードを書いていきます。
開発タブから「Visual Basic」を選択します。
変数の宣言を強制する
ほとんどのプログラミング言語では変数を利用する際に宣言する必要があります。
しかし、VBAは宣言しなくてもエラーを吐きません。
ですが、変数を宣言なしで使える状態だとバグが発生する原因になります。
スペルミスをしていても気がつかないなんてことになりかねません。
オプションから「変数の宣言を強制する」にチェックをいれておきましょう。
1行目に「Option Explicit」という記述が自動で追加されるようになります。
また、僕は「自動構文チェック」はうっとうしいので外しています。
プログラミングに慣れてきたら外すといいでしょう。
標準モジュールの追加
プログラムを記述する標準モジュールというものを追加します。
挿入タブから標準モジュールを選択しましょう。
ほとんどのコードは標準モジュール(Module1)に記述していきますが、外部設計で追加したコマンドボタンに関する記述はSheet1に記述します。
初期化処理の作成
初期化処理に関するプロシージャをModule1に作成します。
プロシージャとは命令の固まりのようなものです。
プロシージャごとに呼び出すことができます。
Option Explicit Public cnt As Integer Dim i As Integer, j As Integer Public Sub 初期化() cnt = 1 '取得したブロック数 'ゲーム画面を初期化 For i = 1 To 20 For j = 1 To 20 Cells(i, j).Interior.ColorIndex = xlNone Next j Next i 'ブロック寿命を初期化 For i = 19 To 35 For j = 2 To 18 Cells(i, j) = 0 Next j Next i End Sub
3行目で宣言したcntは取得した赤いブロックの数を記憶する変数です。
Sheet1からも使えるようにPublicで宣言しています。
グローバル変数と呼ぶこともあるんだ。
3行目で宣言したiとjはループ処理で使う変数です。
通常はその場でしか使わないこれらの変数はプロシージャ内で宣言します。
無駄に変数を参照できる範囲(スコープ)を広げるとバグの原因になるためです。
しかし、他のプロシージャでも何度も2重ループを使うのでプロシージャ外に書きました。
7行目でcntに1を代入します。
この変数は後程、ブロックを消すために利用します。
9~13行目はC3からQ17までのセルを透明の背景色で塗りつぶす処理です。
ゲーム開始前にこの処理を入れることで、前回のゲームプレイで残ったブロックを消去します。
15~20行目はブロック寿命を初期化する処理です。
デバッグ作業時にデータを目で確認できるように、ブロック寿命をB20~R35に保存しています。
ブロック寿命とは僕が作った造語です。
ブロックが1マス動くのを1ターンと考え、ブロックが生成されてからの経過ターン数をブロック寿命と呼んでいます。
ゲーム画面が1枚目の画像のようになっているとき、ブロック寿命は2枚目のようになります。
ブロック寿命とcntを組み合わせることで移動後の不要なブロックを消す処理を後で作ります。
そうしないとこうなってしまうからです。
ゲームを制御する処理の作成
ブロックの移動や赤いブロックの取得、ゲームオーバーといった処理をModule1に記述していきます。
変数の宣言
まずはInteger型の変数を3つ、Boolean型の変数を2つ作成します。
Integer型の変数はX・Y・mukiとします。
Boolean型の変数はplay・finとします。
Boolean型とは「真 = true」と「偽 = false」のどちらかの値を入れる変数です。
これらの変数をModule1にパブリック変数として宣言します。
Public X As Integer, Y As Integer, muki As Integer Public play as Boolean, fin as Boolean
API宣言
処理を指定時間停止できるsleep関数を使用できるようにします。
コンピューターの命令はとても速いので、普通に実行したのではブロックは目にも止まらぬ速さで移動してしまいます。
目にも止まらぬというか、そもそも描写されないです。速すぎて。
なので、ブロックが1マス移動した後処理をループさせる前に指定時間停止させてあげます。
そのために使うのがsleep関数です。
sleep関数を使うことでミリ秒単位で処理を停止できます。
ですが、sleep関数はVBAの関数ではありません。
Windows APIの関数なので、使用するためにはその旨をコンピューターに伝えなければいけません。
Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr) 'sleep関数を使用可能に
モジュールの最初にこの記述を追加してください。
Office2007以前のバージョンで動作させるには書き換える必要があります。
プロシージャの作成
ゲーム中にループさせる処理をプロシージャに書き込みます。
プロシージャの名前はブロックとしています。
各自でわかりやすい名前にしてください。
プロシージャの中にはこのように記述します。
Public Sub ブロック() Do While fin = False 'ブロックが表示されてから移動が発生するごとに1ずつ増える数値をブロック寿命とする For i = 19 To 35 For j = 2 To 18 If Cells(i, j) >= 1 Then Cells(i, j) = Cells(i, j) + 1 End If Next j Next i '"cnt"とブロック寿命を比較しブロックを消去 For i = 19 To 35 For j = 2 To 18 If cnt < Cells(i, j) Then Cells(i - 17, j).Interior.ColorIndex = xlNone Cells(i, j) = 0 End If Next j Next i 'ブロックの移動方向を判定 Select Case muki Case 2 Y = Y + 1 Case 4 X = X - 1 Case 6 X = X + 1 Case 8 Y = Y - 1 End Select 'ゲームオーバーの判定 If Y < 3 Or Y > 17 Or X < 3 Or X > 17 Or Cells(Y, X).Interior.ColorIndex = 5 Then fin = True play = False cnt = cnt - 1 End If '赤いブロックを取得した際の判定 If Cells(Y, X).Interior.ColorIndex = 3 Then cnt = cnt + 1 '空白にランダムで赤いブロックを表示 Dim flag As Boolean flag = False Do Until flag = True Randomize i = Int(Rnd * 15 + 1) + 2 j = Int(Rnd * 15 + 1) + 2 If Cells(i, j).Interior.ColorIndex = xlNone Then Cells(i, j).Interior.ColorIndex = 3 flag = True End If Loop End If Cells(Y, X).Interior.ColorIndex = 5 Cells(Y + 17, X) = 1 'ブロックの寿命 DoEvents Sleep 90'待ち時間の調節 Loop End Sub
コメントを読めば大体わかるようにはしたつもりですが、各処理ごとに個別に解説していきます。
まずは5~12行目の処理について解説します。
'ブロックが表示されてから移動が発生するごとに1ずつ増える数値をブロック寿命とする For i = 19 To 35 For j = 2 To 18 If Cells(i, j) >= 1 Then Cells(i, j) = Cells(i, j) + 1 End If Next j Next i
初期化処理を作成した際に、ブロック寿命をB20~R35に保存することにしましたよね。
2重ループを使ってB20~R35の全てのマスに対してIf文が実行されるようにしています。
64~65行目の処理も合わせて見てください。
Cells(Y, X).Interior.ColorIndex = 5 Cells(Y + 17, X) = 1 'ブロックの寿命
青で塗りつぶしたセルに対応したセルに1を代入しています。
ループして5~12行目が実行された際に、1以上の数値が格納されたセルは+1されます。
この処理によって青いブロックが表示されてから何回処理がループしたかがわかるようになります。
処理がループした回数がわかると、任意のタイミングでブロックを消去できるようになります。
14~22行目の処理を見てください。
'"cnt"とブロック寿命を比較しブロックを消去 For i = 19 To 35 For j = 2 To 18 If cnt < Cells(i, j) Then Cells(i - 17, j).Interior.ColorIndex = xlNone Cells(i, j) = 0 End If Next j Next i
ここでcntという変数が出てきます。
初期化処理を作成した際に宣言しましたね。
cntは赤いブロックを取得した数を記録する変数です。
初期値が0ではなく1に設定したことに注意してください。
If cnt < Cells(i, j) Then が実行される条件を考えてみましょう。
ゲーム開始直後の状態ならcntには1が格納されています。
64~65行目で青くなったセルに対応したセルにも1が格納されます。
このセルがC3、対応したセルはC20、つまりCells(20, 3)だったと仮定します。
ループして5~12行目が実行されるとCells(20, 3)の値は+1されて2になります。
cnt < Cells(20, 3)の条件式を満たすので、Cells(20, 3)の値は0になります。
また、Cells(20 – 17, 3)であるC3のセルは透明になります。
これが赤いブロックを取得する前の状態です。
では、赤いブロックを3個取得した状態も考えてみましょう。
青いブロックの長さは4マス分にならないといけません。
cntに赤いブロックを取得した数を足していく処理を作りましょう。
42~44行目を見てください。
'赤いブロックを取得した際の判定 If Cells(Y, X).Interior.ColorIndex = 3 Then cnt = cnt + 1
これが赤いブロックを取得した際の処理です。
移動後の座標のセルが赤い場合にcntに+1されます。
なお、赤いセルは64行目の処理で青に上書きされます。
ブロックを3個取得した状態なら、cntには4が格納されているはずです。
ブロックが消去されるのは cnt < Cells(20, 3) の条件式を満たす必要がありますから、ブロック寿命が5になったタイミングです。
それまではブロックは消えずに残り続けます。
ブロックを消去する処理が実行されるのを3回後に遅らせることができますね。
読んだだけで100%理解できなくても大丈夫!
次に、ブロックを操作するための命令を作りましょう。
24~34行目を見てください。
'ブロックの移動方向を判定 Select Case muki Case 2 Y = Y + 1 Case 4 X = X - 1 Case 6 X = X + 1 Case 8 Y = Y - 1 End Select
mukiという変数に応じてX・Yの値を増減させています。
これによって処理をするセルの座標を移動させます。
条件を2・4・6・8にしたのは、テンキーと対応させると自分が覚えやすかったからです。
8で上、2で下、4で左、6で右に移動させます。
Y = Y + 1 が下、Y = Y – 1が上に進む点が勘違いしやすいので注意してください。
mukiに変数を代入するための処理も作りましょう。
外部設計で4つのボタンを作りましたよね。
デザインモードに切り替えて、ダブルクリックしてください。
すると、Sheet1というコードを入力する画面が開くはずです。
Private Sub Button右_Click() If muki <> 4 Then muki = 6 End If End Sub Private Sub Button下_Click() If muki <> 8 Then muki = 2 End If End Sub Private Sub Button左_Click() If muki <> 6 Then muki = 4 End If End Sub Private Sub Button上_Click() If muki <> 2 Then muki = 8 End If End Sub
それぞれのボタンにこのようなコードを追加します。
進行方向と逆に移動してしまうと、自身とぶつかりゲームオーバーしてしまうので防ぎます。
僕はオブジェクト名をわかりやすく変更しています。
コピペでは動かないので注意が必要です。
デフォルトではCommandButton○になっているので、変更しておきましょう。
方向ボタンはこれで完成です。
しかし、このままではゲーム中に押しても何も反応しません。
プログラムをループさせてる間、こちらの処理は実行されません。
ゲームオーバーして処理を抜けた後にようやく反映されます。
なので、DoEvents関数を使って処理に割りこめるようにします。
67行目にDoEventsと書いておきましょう。
次はゲームオーバーの処理を作っていきます。
36~40行目を見てください。
'ゲームオーバーの判定 If Y < 3 Or Y > 17 Or X < 3 Or X > 17 Or Cells(Y, X).Interior.ColorIndex = 5 Then fin = True play = False End If
指定した範囲からはみ出して青く塗りつぶしたら、finをTrueに切り替えてループから抜けます。
ゲームをプレイ中かどうかを判別する変数、playはFalseにします。
finとplay、1つにまとめられない?
僕もそう思います。
じゃあ直そうよ(‘Д’)
赤いブロックをランダムに表示させる処理を作ります。
46~62行目を見てください。
'空白にランダムで赤いブロックを表示 Dim flag As Boolean flag = False Do Until flag = True Randomize i = Int(Rnd * 15 + 1) + 2 j = Int(Rnd * 15 + 1) + 2 If Cells(i, j).Interior.ColorIndex = xlNone Then Cells(i, j).Interior.ColorIndex = 3 flag = True End If Loop End If
透明なセルの座標を取得できるまで50~61行目を繰り返します。
Rndは0以上1未満の乱数を発生させる関数です。
例えば1~10の整数をランダムに取得したい場合、Int(Rnd * 10 + 1) となります。
これは公式のようなものなので覚えてください。
今回は3~17の整数を取得できるようにしています。
Randomizeはランダムにシード値を指定する処理です。
シード値とはRndで乱数を発生させる際に使用する数値です。
Randomizeを入れないと、Excel起動時にシード値が同じになってしまいます。
忘れずに書きましょう。
最後にSleep関数を記述します。
69行目の部分ですね。
Sleep ○○ と記述することで処理を指定時間停止できます。
ここの数値を変更することでゲームの難易度を調節できます。
最後にゲームを開始するスタートボタンを作ろう。
スタートボタンの作成
スタートボタンに以下のようなコードを記述します。
Private Sub CommandButton1_Click() If play = False Then Call 初期化 Call ランダム配置 fin = False play = True X = 10 Y = 10 muki = 2 Call ブロック End If End Sub
最初にCall文を使って「初期化」のプロシージャを呼び出します。
1つ目の赤いブロックを配置する必要があるので、ランダム配置というプロシージャを新たに作りました。
Public Sub ランダム配置() '空白にランダムで赤いブロックを表示 Dim flag As Boolean flag = False Do Until flag = True Randomize i = Int(Rnd * 15 + 1) + 2 j = Int(Rnd * 15 + 1) + 2 If Cells(i, j).Interior.ColorIndex = xlNone Then Cells(i, j).Interior.ColorIndex = 3 flag = True End If Loop End Sub
先ほど作った「ブロック」のプロシージャに書いたものと全く同じです。
本来は同じコードを複数回書くことは美しくないのですが、DoEvents関数を使用する都合上こうなりました。
後はゲーム開始時の座標と進行方向を格納して、「ブロック」のプロシージャを呼び出します。
これでゲームとしてプレイできるようになりました。
あとがき
無事にゲームを動作させることはできましたか?
今回は基礎編ということで、プレイ可能になるまでのプログラムを組みました。
次回はキーボードからの操作に対応させる方法を解説します。
また、難易度選択やランキング機能も追加していく予定です。
授業中の暇つぶしを目的に作成しました。