ExcelVBAゲームプログラミング?

初心者でもきっとできる!
Excelさえ持っていれば特別なソフトは不要!
すぐにでも始められる簡単ゲームプログラミング!
今すぐ始めよう!

サンプルやゲームのダウンロードができる別館も好評運営中です。
ご意見やご質問、ゲームの感想等は掲示板までお気軽に。是非、皆さんの声を聞かせてください。運営、開発の励みになります。



各種ダウンロードはコチラ ↓ 意見・感想・質問はコチラ ↓
影倉庫 Shadow warehouse サポート掲示板
ブログの全体像はコチラ ↓ リンクのページはコチラ ↓
サイトマップ 自分本位なリンク


スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。






Chapter.108 [ ブロック崩しゲーム3:ベクトルとは ]

■ベクトルを用いたボールの移動処理

今回は、ベクトルというものをボールの移動処理に使います。
ベクトルは数学の知識がある程度必要になりますが、いったん習得できれば確実にプログラミングの幅が広がります。まずは、ベクトルとは何か、そしてベクトルを用いることによってどのような利点があるのか、それから考えていきましょう。


■ベクトルとは

ベクトルを数学的に説明すると「向きと大きさを持った量」となります。しかしこれではいまいちピンときませんね。もっと噛み砕いて言うと、ベクトルとは方向チカラの大きさを表すことができる単位のようなものです。

二次元の世界でベクトルを表す際には、横方向を X 、縦方向を Y とし、これらをセットで考えます。三次元の場合はこれに奥行きを表す Z を加えてベクトルを表現します。
ベクトルを図に描いて表す場合は、矢印を用いて表現します。

矢印の向きが、そのままベクトルの向きです。そして、矢印の長さが長いほど、ベクトルの強さが大きくなります。以下の画像を見ると、ふたつのベクトルが、矢印を用いて描かれていますね。ベクトル A も、ベクトル B も、矢印が示す方向は同じですよね。つまりこのふたつのベクトルの持つ向きは全く同じです。しかし、矢印の長さは違いますよね。ですからこのふたつのベクトルのチカラの大きさは異なります。

600.gif

さて、ここまで読んで、ベクトルが何であるか理解できたでしょうか。
恐らく、無理でしょう。私の説明が悪いという可能性も否定できませんが、いきなりベクトルが何かを理解しろと言われても、なかなか難しいと思います。

ラジアンを解説したときにも書きましたが、プログラミングの技術としてのベクトルを考える際には、かならずしも数学的な知識を完全に理解している必要はありません。あくまでも、プログラミングでどのように活用するか、これが大事ですね。
今回も、ベクトルをもう少しだけ詳しく見ていきますので、そこからプログラミングへの活用方法を考えていきましょう。


■ベクトルをさらに紐解く

それでは、ベクトルをもう少し詳しく見ていきます。

ベクトルは、複数の数値を使って表されるものです。括弧の中にカンマ区切りで数値を並べます。ここで大事なのはベクトルが『 複数の数値 』を扱うという事実です。

例えば、目の前にりんごがふたつあります、ということを表現するためには、数字がひとつあればこと足りますね。

りんご = 2個

こんなふうに、数字ひとつで表現することができますね。
しかし、例えば進行方向を表すためには、横の移動量と縦の移動量というふたつの情報を持っていなくてはいけません。そこで、ベクトルを用いるわけです。

横移動 = 3m
縦移動 = 4m

※ベクトルで表すと…… (3m, 4m)

こうなりますね。

要するに、ベクトルとはつまり、複数の情報をパッケージ化してひとつにまとめたもの、と考えることができます。進行方向を表すためには二次元であれば縦と横というふたつの情報が必要です。それをひとくくりにして扱いやすくするために、ベクトルが用いられるということなのですね。

さて、ここでちょっと思い出してみてください。
VBA でプログラミングを行う上で、皆さんは既に『情報をパッケージ化してまとめる』ということを何度も経験しているはずです。思い当たることはありませんか?

そうです。
構造体変数 』ですね。

ベクトルを難しく考えすぎると、この単純なことに案外気がつかないものです。ベクトルとは要するに、構造体を使って表されるデータの集合に過ぎないのです。このことがわかれば、ベクトルをプログラミングに活用する方法もおのずと見えてきます。


■実際に使ってみる

さて、それでは実際にプログラミングにベクトルをどう使えばよいのかを考えましょう。

先ほども書いたように、ベクトルは構造体を用いて表現します。まずは、ベクトルを表す構造体を定義します。

Type Vector
    X As Single
    Y As Single
End Type

この Vector という構造体は、XY 、というふたつの要素を持った構造体です。X が横方向の移動量を表し、Y が縦方向の移動量を表します。

このふたつの要素に、縦の移動量と横の移動量を与え、それを元に処理するようにします。

例えば、45度の方角に向かって移動するベクトルは次のようにして導き出します。

Type Vector 'ベクトル構造体の定義
    X As Single
    Y As Single
End Type

Const PI As Single = 3.14159 '円周率を定数で宣言

'----------------------------------------------

Sub Vector_Set()

    Dim Vec As Vector 'ベクトル変数
    Dim Rad As Single 'ラジアン
    Dim Ang As Long '度数
    
    Ang = 45 '変数 Ang に度数を入れる
    
    Rad = Ang * PI / 180 '度数からラジアンへの変換
    
    Vec.X = Cos(Rad) 'コサインを用いて横移動量を算出
    Vec.Y = Sin(Rad) 'サインを用いて縦移動量を算出
    
End Sub

これは、以前の講座でも解説したことがあるラジアンを用いた処理です。
ラジアンや、サイン・コサインの使い方についてはそちらを参照していただければ詳しく解説しています。(Chapter.64を参照)

上記のように処理すると、構造体変数 Vec には縦の移動量と横の移動量が入ります。これこそがベクトルです。

あとは、このベクトルを用いてループ処理するなどすれば、任意の角度に向かって移動するプログラムが出来上がります。一見すると難しそうに見えますが、始めからひとつひとつ考え方を整理していけば、案外簡単にベクトルを扱うことができるでしょう。


■まとめ

さて、ベクトルについて理解できたでしょうか。
数学的なベクトルの知識について深く探求することも、時には必要です。しかし、実際にプログラミングでベクトルを扱うことには、必ずしも数学的な知識は必要ありません。
どのように活用すればいいのか、それをしっかり押さえておけば、あとはおいおい勉強していけばよいのです。まずは自分で使ってみること、そして結果として動くプログラムができあがるという感動を味わうこと、これが大事です。

今回はベクトルの概念的な説明が多かったので、具体的なコードは少ないですが、今回の内容が今後のブロック崩し作成講座の中でとても重要になってきます。次回以降、スムーズに理解していけるように、しっかりとベクトルの基本をつかんでおいてくださいね。


■格言

ベクトルは複数の数値を扱う
ラジアンなどを活用してベクトルを算出


以前の講座でも角度に関する処理を行いましたが、あれも、実はベクトルを用いた処理だったのですね。


スポンサーサイト






Chapter.109 [ ブロック崩しゲーム4:変数や定数の宣言 ]

■いよいよ作成開始

前回は、ベクトルの簡単な概念の解説をしました。ベクトルというとなんとなく難しいイメージがありますが、プログラミングではベクトルの意味よりも使い方が重要です。概念的な部分は、まずは曖昧でも構いません。実際に使いながら、徐々に理解していけばいいでしょう。

さて、今回からは実際にプログラミングを開始します。焦らず落ち着いて、ひとつひとつこなしていきましょう。

今回もいつものように、まずは初期化処理から実装していきます。
ブロック崩しの初期化処理では、ボールやバーの位置を整えたり、ブロックを配置したりといった処理を行います。また、一連の初期化処理がしやすいようにするために、定数なども活用しながら処理していきます。今回の講座では、主に変数などの宣言部分を中心に解説します。


■変数と定数の定義

それでは今回のブロック崩しに登場する、変数や定数を見てみましょう。
ポイントとなるのは、ボールやバーなどの大きさを、定数であらかじめ定義しておくという部分です。大きさをあらかじめ定数で管理するようにしておくことで、あとあと様々な恩恵が受けられます。どんな恩恵を受けられるのかは、あとで詳しく説明します。まずは標準モジュールをひとつ挿入し、次のように記述しましょう。

'■APIの宣言
Declare Function GetTickCount Lib "kernel32" () As Long
Declare Function GetAsyncKeyState Lib "user32" (ByVal vkey As Long) As Long
Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMillsecounds As Long)

'■構造体の定義
Type Balls 'ボール管理構造体
    aX As Single
    aY As Single
    bX As Single
    bY As Single
    vX As Single
    vY As Single
    Sp As Single
    Vis As Boolean
End Type

Type Blocks 'ブロック管理構造体
    X As Single
    Y As Single
    Vis As Boolean
End Type

Type Vector 'ベクトル構造体
    X As Single
    Y As Single
End Type

'■変数の宣言
Public Ball As Balls 'ボール
Public Block(1 To 25) As Blocks 'ブロック(配列)
Public Bar As Vector 'バー

'■定数の宣言
Const W_Field As Single = 250 'フィールド横幅
Const H_Field As Single = 300 'フィールド縦幅
Const W_Ball As Single = 10 'ボールの大きさ
Const W_Block As Single = 50 'ブロック横幅
Const H_Block As Single = 20 'ブロック縦幅
Const T_Block As Single = 20 '最上段ブロックの位置
Const W_Bar As Single = 48 'バーの横幅
Const H_Bar As Single = 12 'バーの縦幅
Const T_Bar As Single = 280 'バーの位置(Top)

Const PI As Single = 3.14159 '円周率

いきなり盛りだくさんですね。
ですが、特に難しいことはありません。順番に見ていきましょう。

始めに登場するのが API の宣言です。

Declare Function GetTickCount Lib "kernel32" () As Long
Declare Function GetAsyncKeyState Lib "user32" (ByVal vkey As Long) As Long
Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMillsecounds As Long)

今回は、みっつの API を使います。
GetTickCount は、パソコンが起動してから現在までの時間を、ミリ秒単位で返してくれる API です。同期処理を行うためにある程度精度の高いタイマーが必要になりますので、この API を使います。GetAsyncKeyState はキーの入力を検知するために使います。講座内でなんども使っているのでお馴染みですね。そしてもうひとつの API が Sleep です。こちらもなんども登場しているので大丈夫でしょう。Sleep はミリ秒単位でスレッドを停止させる API で、ゲームの処理が高負荷になりすぎないようにするために欠かせません。

次に登場するのが、構造体の定義ですね。

Type Balls 'ボール管理構造体
    aX As Single
    aY As Single
    bX As Single
    bY As Single
    vX As Single
    vY As Single
    Sp As Single
    Vis As Boolean
End Type

Type Blocks 'ブロック管理構造体
    X As Single
    Y As Single
    Vis As Boolean
End Type

Type Vector 'ベクトル構造体
    X As Single
    Y As Single
End Type

ここでは前回解説したベクトルの定義のほか、ボールやブロックなどを扱いやすくするために、それぞれ構造体として定義しています。

わかりにくいのは、ボール管理構造体だと思います。ボールひとつ管理するために、なんでこんなにも多くの項目が必要なのか、不思議ですよね。よく見てみると、X と Y というアルファベットがたくさん出てきています。これはおいおい説明しますが、ブロックなどとの衝突判定をする際に、これらの変数が非常に重要な役割を果たします。まだ意味はわからないでしょうが、まずは同じように構造体を定義しておいてください。

さらに変数の宣言が続きます。
先ほど定義した構造体を用いて、ボールなどを扱う変数を宣言します。

Public Ball As Balls 'ボール
Public Block(1 To 25) As Blocks 'ブロック(配列)
Public Bar As Vector 'バー

ここは簡単ですね。先に定義しておいた構造体をそのまま変数の宣言に使っているだけです。

さて、ちょっと長いですが、最後に定数の宣言です。

Const W_Field As Single = 250 'フィールド横幅
Const H_Field As Single = 300 'フィールド縦幅
Const W_Ball As Single = 10 'ボールの大きさ
Const W_Block As Single = 50 'ブロック横幅
Const H_Block As Single = 20 'ブロック縦幅
Const T_Block As Single = 20 '最上段ブロックの位置
Const W_Bar As Single = 48 'バーの横幅
Const H_Bar As Single = 12 'バーの縦幅
Const T_Bar As Single = 280 'バーの位置(Top)

Const PI As Single = 3.14159 '円周率

ここでは、フィールド・ボール・ブロック・バー、それぞれの位置や大きさを定義するための定数を宣言しています。なぜ定数をこれほど大量に宣言するのか、その理由がみなさんはわかるでしょうか。

先ほどちらっと書きましたが、こうしておくことで様々な恩恵が受けられます。
定数を使うことの恩恵の第一は、わかりやすさが向上するということです。例えば、ブロックの大きさなどを数字で管理していると、うっかり大きさがわからなくなってしまうことがあるかもしれません。10 だったか、それとも 20 だったか、どっちだったっけ、なんてことになるかもしれませんよね。これが定数として管理されていれば、こまかい数値まで覚えておく必要はありません。コードを記述する際には、定数を使えばいいのですからね。

さらに、定数を利用している場合にはあとあとプログラムを修正する際に非常に便利です。衝突判定などを行う際には、頻繁にブロックやボールの大きさを使った計算が出てきます。もし、あとからボールを少し大きくしたいとか、ブロックを少し小さくしたいなどと思ったときに、全ての計算箇所を手で修正していくのはとても大変です。定数を使ってあらかじめコードを記述しておけば、修正するのは定数の宣言部分だけで済みますから、手間が大幅にカットできます。

最初だけちょっと手間ですが、しっかり定数を宣言しておくことで、あとあと必ず役に立ちますので、面倒ですががんばってコードを記述しておきましょう。


■ボール構造体の秘密

さて、先ほど定義したボール用の構造体について、もう少しだけ突っ込んだ解説をしましょう。もう一度、ボール用の構造体を見てください。

Type Balls 'ボール管理構造体
    aX As Single
    aY As Single
    bX As Single
    bY As Single
    vX As Single
    vY As Single
    Sp As Single
    Vis As Boolean
End Type

これを見ると、X や Y が名前についている要素がたくさんあるのがわかりますね。そして、色分けしてみるとわかりやすいですが、それぞれ X と Y が対になっているのがわかると思います。

小文字の a から始まるもの、同様に、b から始まるものと、v から始まるものがありますね。

実は、ボールを処理する際、移動処理を考えるだけでよいのなら、こんなにたくさんの要素は必要ありません。前回までで説明したように、ベクトルを使って処理するために必要なのはせいぜい X と Y 、ふたつの要素だけです。
にもかかわらず、これほどたくさんの要素を定義しているのには、キチンと理由があります。その理由とは『 衝突判定を行うために必要だから 』なんです。

今回のブロック崩しゲームでは、衝突判定に『 線と線 』での判定を使います。ブロックの辺にあたる直線と、ボールが進む軌道の直線、このふたつの直線が交差しているかどうかを調べ、それによって衝突判定を行うのです。

610.gif

そして、この方法を実現するためには、ボールの軌道がデータとして存在しなくてはいけませんよね。そのために、これだけたくさんの要素が必要になるのです。

小文字の a から始まる X と Y は、after 、つまりボールが移動したあとの座標を表します。
そして、小文字の b から始まる X と Y は、before 、つまりボールが移動する前の座標を表します。

これを図にすると次のようになりますね。

611.gif

A と B 、ふたつの座標を線で結ぶと、ボールがどのように移動したのかその軌跡がわかります。ここでできあがる直線と、ブロックの辺との交差判定を行うことによって、ボールがブロックと衝突しているのかどうかを判定できるわけです。

ブロック崩しでは、衝突判定が最大の難関になります。線と線の交差を判定するなんて、考えただけでもかなり複雑になりそうな気がしてしまいますね。ですが、この方法が恐らく精度的には一番高いです。難しいのは確かですが、そこはきちんと十分な解説を行いながら進めていきますので、がんばってついてきてほしいと思います。


■まとめ

変数と定数を宣言しただけなのに、かなりボリュームのある内容になってしまいましたね。途中、衝突判定の話なども出ましたので、かなりイメージするのが大変だったのではないかと思います。

今後、ブロック崩しを作成していくなかで、比較的難しい概念や処理がたくさん出てきます。ですが、落ち着いて、焦らず理解していくことが大事です。今回はまだ変数などをひととおり用意しただけに過ぎません。それぞれの変数の役割などは、講座を進めていくうちに徐々に明らかになっていきます。まずは今回の内容をしっかりと理解し、次回以降につまずかないようにしておいてくださいね。



■格言

定数を使ってわかりやすく
衝突判定を踏まえて構造体を定義


あまり難しく考えすぎないようにしましょうね。
落ち着いてやれば、大丈夫です。







Chapter.110 [ ブロック崩しゲーム5:初期化処理の実装 ]

■初期化処理を実装する

前回の講座では、Windows API の宣言や変数・定数の宣言などを解説しました。
特別、難しいことをしているわけではありませんでしたが、コードの行数が結構あったのでちょっと面倒でしたね。ただし、ここは全ての根本になる大切な部分ですので、しっかりとやっておきましょう。

さて、今回はゲームの初期化処理について解説します。
ブロック崩しゲームの初期化処理では、ボールやバーの初期配置、各種変数の初期化などを行います。今回の初期化処理でポイントとなるのは、初期化処理自体を完全に単体のプロシージャで行うのではなく、用途に合わせて細分化する、というところになります。

初期化処理を細分化?
さて、実際に中身を見ながら、考えていくことにしましょう。


■初期化のタイミングと役割

ブロック崩しゲームの初期化には、いくつかの種類があると考えることができます。
ここでいう種類というのは、初期化処理が呼び出されるタイミングの違いによる種類です。

例えば、ボールやバーを画面中央において、これからゲームがスタートできるようにする初期化処理を作ったとします。この初期化処理は、ゲームがスタートする前はもちろん、ボールを落としてしまったあとにも呼び出す必要がありますよね。ミスしたあとの再スタート時にも、ボールやバーの初期化は必要となるわけです。

しかし、ブロックの初期配置は、最初の最初、本当のゲームのスタート時にしか呼び出しません。そうしておかないとミスをするたびに、毎回ブロックが復活してしまうことになります。これではゲームをクリアするのが大変ですし、なによりゲームとして変ですよね。

このように、ゲームの種類にもよりますが、初期化処理には呼び出されるべきタイミングの違いがあります。このことを踏まえると、全ての初期化処理をひとつのプロシージャだけで処理するのは間違いだということがわかりますね。複数の初期化処理を用途に合わせて準備しておくことで、ゲーム全体の流れをうまくコントロールすることができるのですね。

今回のブロック崩しでは、ボールやバーなどの初期化を行うプロシージャと、ブロックの配置を行うプロシージャの、ふたつのプロシージャを用意していきます。


■ボールの初期化から

さて、早速ですが初期化処理を実際に記述していきます。

まずは、ボールやバーを初期化するほうのプロシージャから作成しましょう。先にコードを掲載しますので、ちょっと見てください。

Sub Init()

    Dim L As Long
    Dim S As Single
    
    Randomize
    
    With Ball
        L = Int(Rnd * 30) + 30
        S = L * PI / 180
        .aX = 0
        .aY = 0
        .bX = W_Field / 2
        .bY = T_Bar - W_Ball / 2
        .vX = Cos(S)
        .vY = Sin(S)
        .Sp = 3
        .Vis = True
        If Int(Rnd * 2) = 0 Then .vX = -.vX
    End With
    
    With Bar
        .X = W_Field / 2
        .Y = T_Bar + H_Bar / 2
    End With
    
    With UserForm1
        .Ima_bar.Left = (W_Field - W_Bar) / 2
        .Ima_bar.Top = T_Bar
        .Ima_ball.Left = (W_Field - W_Ball) / 2
        .Ima_ball.Top = T_Bar - W_Ball
    End With
        
End Sub

結構な行数がありますね。With ステートメントを活用しながら設定を行っているのがわかると思います。具体的には『ボール構造体への初期値のセット』、『バー構造体への初期値のセット』、『ユーザーフォーム上のコントロールの初期配置』というみっつのおおまかなくくりで処理しています。


最初に登場するのが、ボール管理構造体へのデータのセットです。

ここで、ボール管理構造体の各要素の役割を確認しておきましょう。

(aX, aY) …… ボールの移動後の座標
(bX, bY) …… ボールの移動前の座標
(vX, vY) …… ボールのベクトル
Sp …… ボールの移動スピード
Vis …… ボールが見えているかどうか

たくさんの要素があって紛らわしいですが、よく役割を考えながら、落ち着いて見ていきましょう。

小文字の a から始まっている X と Y は、ボールの移動処理を行う際に使います。ボールが移動したあとの座標をこの変数で管理します。同様に、小文字の b から始まっている X と Y もボールの移動に関連した処理で使いますが、こちらはボールが移動する前の座標を管理します。

小文字の v から始まっている X と Y がありますね。
これはベクトルです。ボールの移動ベクトルを管理するのが vX と vY です。そしてボールの移動スピードを管理する Sp 、ボールの今現在の可視状態を管理する Vis 、これらがボール管理構造体の全容です。

上記の役割を踏まえたうえで、初期化処理を見てください。

    With Ball
        L = Int(Rnd * 30) + 30 '①
        S = L * PI / 180 '②
        .aX = 0
        .aY = 0
        .bX = W_Field / 2
        .bY = T_Bar - W_Ball / 2
        .vX = Cos(S)
        .vY = Sin(S)
        .Sp = 3
        .Vis = True
        If Int(Rnd * 2) = 0 Then .vX = -.vX '③
    End With

①の部分では、乱数を用いて何らかの処理を行っていますね。ここでは、30~59 の間の乱数を取得しています。仕組みはよく見るとわかると思います。『 Int(Rnd * 30) 』の部分で 0~29 までの乱数が生成され、そこに30を加算しているので、結果的に 30~59 の間の乱数が取得されるようになっています。

ここで得られた数値は、ボールの最初の発射角度になります。毎回同じ方向に向かってボールを打ち出すのではなく、ランダムに角度が変更されるようになっているわけですね。ただ、ボールが打ち出される角度はある程度限定しておかないと、真横にすっとんでいったりしてしまう可能性があります。ですから、乱数を使って 30~59 の範囲に限定して処理しているのです。

②の部分では、先に得られたボールの発射角度を、ラジアンに変換しています。

そこから先は、順次必要な値をセットしているだけです。
ボールの移動後の座標を表す aX と aY は、初期値は 0 です。まだ一切移動を行っていないので、ここは深く考えずに 0 を入れておいて問題ありません。
ボールの移動前の座標は、bX と bY でしたね。bX には、ボールの横位置が入ります。ボールは初期状態では画面の中央(ちょうどバーの上に乗っているような感じ)に置かれるようにしますので、フィールドの幅を半分にした数値を設定します。W_Field は、フィールドの横幅を表す定数でしたね。これを 2 で割った数を bX には指定します。
bY の場合はちょっとわかりにくいかもしれません。T_Bar は、バーの高さを表す定数です。そしてそこからボールの大きさを表す定数W_Ball の半分を引いています。こうすることで、ちょうどバーの上にボールが乗っかっているような感じになります。

620.gif

さて、続いては vX と vY 、つまりベクトルです。
ベクトルは最初に乱数を使って算出していたラジアンを使って求めます。横向きのベクトルを表す vX には、コサインを使って求めた数値を入れます。同様に vY にはサインを使って求めた数値を入れます。これは簡単ですね。

そして、ボールの移動スピードの初期値を Sp に入れます。さらに、ボールは初期状態では見えていないとおかしいですから Vis にも True が入りますね。

最後に③の部分。ここでは、再度乱数を用いた処理を行っていますね。
ボールのベクトルの向きを、50%の確立で反転させています。最初に vX に入る値は必ずプラスの数値になります。しかしこのままでは、毎回必ず右方向に向かってボールが移動することになってしまいますので、半分の確立で符号を反転させることによって、ランダムに左方向へも移動するようにしているのですね。

ボールの初期化処理だけでも、これだけ濃い内容になってしまいました。
焦らず、ひとつひとつ意味を考えながら理解していってくださいね。


■続いてバーの初期化

さて、続いてはバーの初期化です。こちらはボールに比べるとかなり簡易です。

    With Bar
        .X = W_Field / 2
        .Y = T_Bar + H_Bar / 2
    End With

バーのほうは、必要な情報がその位置だけなので、ここではそれを設定しています。ボールのときと同じように、ここでも定数をうまく活用して値を設定しています。
横位置を表す X の要素には、フィールドの幅の半分の数値を設定します。これはボールのときと全く同じですね。画面の中央に横位置を合わせているわけです。縦位置は、バーの高さを表す定数 T_Bar に、バーの縦幅の半分をプラスして設定しています。


■最後にユーザーフォーム上の設定を行う

さて、ボールとバーの管理構造体にデータをセットすることができました。あとは、ユーザーフォーム上にあるコントロールの初期配置を行えば初期化は完了です。

    With UserForm1
        .Ima_bar.Left = (W_Field - W_Bar) / 2 'バーの横位置(Left)
        .Ima_bar.Top = T_Bar 'バーの縦位置(Top)
        .Ima_ball.Left = (W_Field - W_Ball) / 2 'ボールの横位置(Left)
        .Ima_ball.Top = T_Bar - W_Ball 'ボールの縦位置(Top)
    End With

ここでも、イヤと言うほど定数がたくさん登場しています。

アルファベットの W から始まる定数は、全て横幅を表す定数であり、アルファベットの H から始まる定数は、縦の幅を表す定数です。このことを踏まえて落ち着いてみていけば、おおよそ何をやっているのかはわかると思います。

定数の宣言を行った前回の講座でも書きましたが、定数を用いることの利点が今回解説したプロシージャを見るとよくわかります。一見すると、定数を利用したことによって文字数が増えますから、わかりにくく感じるかもしれません。しかし、今後、バーの大きさやボールの大きさを変更したいと思ったとき、今回解説したプロシージャには一切手を加える必要はありません。定数の宣言部分で、設定している値を変更すればいいだけなのです。

定数を用いた処理を記述することに対して、最初はどうしても戸惑いを感じるかもしれません。しかし、これには是非、慣れておいてほしいと思います。あとあとのメンテナンス性を考える場合、定数を利用したコードの記述は大きな武器になります。
今回の初期化プロシージャは、たとえフィールドの大きさが変更されようが、ボールの大きさが変更されようが、はたまたバーの大きさが変更されようが、一切修正することなく対応できる完璧な初期化プロシージャになっています。これは、定数を用いてあらゆる処理を行っていることによって、初めて可能になることなのです。


■まとめ

さて、今回は初期化処理ひとつを取り上げて、じっくりと解説してきましたがどうでしたでしょうか。ボールの初期化の部分は、かなりわかりにくく感じると思いますが、落ち着いて考えてみてくださいね。構造体の各要素の役割をきちんと理解し、定数を使って値を設定していけばおのずとうまくいくはずです。実際にゲームが動くようになってくると、もっとイメージが湧きやすいと思うので、あとからじっくり見直してみるというのもありだと思います。

最初に書いたとおり、今回のブロック崩しでは、初期化ルーチンをブロックの初期化と、それ以外の初期化に細分化して作ります。今回作成した初期化処理はブロック以外のボールとバーの初期化を行いましたね。次回は、ブロックの初期化処理に取り組んでいく予定です。


■格言

初期化処理を目的別に作成する
構造体の要素の意味を踏まえて考える
定数を用いて処理する


次回のブロックの初期化は、ワークシートにあるデータを拾ってきて処理する、というのをやってみたいと思いますので、お楽しみに。








Chapter.111 [ ブロック崩しゲーム6:ブロックの配置 ]

■ブロックの初期配置

前回はボールやバーなどの初期化について解説しました。
定数をうまく活用することによって、メンテナンスのしやすいプログラムを作ることができるということも、実感できたのではないかと思います。

今回は、もうひとつの初期化処理、ブロックの初期化を行います。
ブロックの初期化処理では、Excel のワークシートを活用して処理します。こうすることによって、セルに入力されている情報を変更するだけで、簡単にブロックの配置が変更できるようになります。

今回作成するブロック崩しは、最低限の機能だけを搭載したものになる予定です。ですから、複数のステージを順番にクリアしていくようなつくりにはしません。
ただ、ブロックの配置を行うプロシージャは、複数ステージに対応できるように設計します。それをどう活用するのかは、これをご覧になっている皆さんの自由です。普通にステージクリア型のブロック崩しを作ってもいいでしょうし、もっと違ったアプローチで活用するのもいいでしょう。

ゲームのデザインや特徴は、開発する側が試行錯誤して決めるものです。これは、今回作成しているブロック崩しに限ったことではありません。私は講座を通してノウハウやテクニックを公開していますが、それはあくまでも入り口までの案内をしているだけに過ぎません。そこから先、どのように道筋を立てて進んでいくのかは、皆さん次第です。


■ブロックの図面を作る

さて、それでは早速ブロックの初期化処理を考えていきましょう。

先にも書いたように、今回はワークシートをうまく活用しながら処理していきます。

ブロック崩しを作成しているブックの、ワークシートのどれかひとつをブロックの配置図専用のシートとして使います。そこからデータを読み込んでブロックが配置されるようにするわけです。

通常、Excel の基本設定をいじっていなければ、ブックに含まれるワークシートの数は自動的にみっつになります。今回はそのうちのひとつを『 map 』という名前に変更して、そこにブロックの配置図を作ります。

630+.gif

ブロックの配置図は、5 × 5 の合計 25 個のセルを使って表します。セルの A1 から E5 までを使って、ひとつ分のステージが表現できることになりますね。
そして、ブロックが存在するかどうかは、セルに入力されている数字で判断します。今回は数字が 0 より大きければ、そこにブロックがあるという判断で処理してみることにします。

ワークシートのほうは、罫線を引くなどして、ブロックの配置をデザインしやすいようにしておきます。ここで条件付き書式などを併用すると、デザインのしやすさが格段に向上しますので、オススメです。

631.gif

条件付き書式を以下のように設定することで、ワークシート上でブロックの配置をデザインする際に、非常にわかりやすくなります。

633.gif

    ▼

632.gif


■ブロックデータ読み込み

さて、ワークシートの準備が完了したら、そこからデータを読み込みながらブロックの初期化処理を行っていきます。

ポイントとなるのは、計算方法。
かなり複雑になりますが、落ち着いて考えましょう。

Sub Init_Block(Stage As Long)

    Dim L As Long
    Dim LL As Long
    Dim Cell_Row As Long
    Dim Cell_Col As Long
    
    For L = 0 To 24
        With ThisWorkbook.Worksheets("map")
            Cell_Row = (Stage * 5 - 4) + (L \ 5)
            Cell_Col = (L Mod 5) + 1
            If .Cells(Cell_Row, Cell_Col).Value > 0 Then
                With Block(L + 1)
                    LL = L Mod 5
                    .X = (W_Block / 2) + (LL * W_Block)
                    LL = L \ 5
                    .Y = T_Block + (H_Block / 2) + (LL * H_Block)
                    .Vis = True
                    UserForm1.Controls("Label" & L + 1).Visible = True
                End With
            Else
                With Block(L + 1)
                    .Vis = False
                    UserForm1.Controls("Label" & L + 1).Visible = False
                End With
            End If
        End With
    Next

End Sub

ちょっと長いプロシージャですが、焦らず見ていきましょう。
まず最初にプロシージャ名のところを見てください。このプロシージャの名前は Init_Block となっています。ブロックの初期化処理ですので、このようなネーミングになっています。そして、そこに続く括弧の中に『 Stage As Long 』とあります。これは、このプロシージャが複数ステージに対応できるよう設計されていることを表しています。

このプロシージャは、引数をひとつ取るようになっており、受け取った引数はステージ数を表します。先ほどワークシートに設計したブロックの配置図はステージがひとつ分しかありませんでしたが、下方向に同じレイアウトの配置図を作成しておくことで、複数のステージをこのプロシージャひとつで読み込むことが可能です。

それでは肝心の中身を見ていきます。
まず、変数の宣言ですが、このプロシージャではよっつの変数を宣言しています。変数 L と、変数 LL は、計算に使うための汎用変数です。そして変数 Cell_Row と変数 Cell_Col は、セルの参照を行う計算の中で使います。

このプロシージャではまず、汎用として宣言した変数 L を使った繰返し処理が始まります。For 文を使って、0 ~ 24 までの繰り返し処理を行っているのがわかると思います。
そして、繰り返し処理の中では、まずふたつの変数に値を取得しています。ここは紛らわしいので、ゆっくり見ていきましょう。

Cell_Row = (Stage * 5 - 4) + (L \ 5)
Cell_Col = (L Mod 5) + 1

Cell_Row は、その名の通りセルの行数を取得するための変数です。上の計算をよく見てみると、引数として受け取ったステージ数( Stage )と繰り返し処理のカウンタ( L )を使っていますね。
ステージ数を使っているほうの計算『 (Stage * 5 - 4) 』では、変数 Stage の値が増えるたびに、1、6、11、16……、というふうに増えていきますね。そしてカウンタ変数 L は『 \ 』という演算子を使って計算していますね。この演算子は、割り算した結果から、整数部分だけを抜き出してくれる演算子です。たとえば変数 L の値が 3 だとすると、3 ÷ 5 = 0.6 となりますよね。この場合は、\ 演算子は 0 を返してきます。変数 L が 8 だったらどうでしょうか。この場合は、8 ÷ 5 = 1.6 となるため \ 演算子を使った計算では 1 が返ってきます。

Cell_Colのほうはどうでしょうか。
こちらは Mod 演算子を使っていますね。この演算子は以前にも何度か使いました。割り算した結果から余りだけを返してくれる演算子ですね。このように計算した結果をセルの列数として使います。

さて、これらのふたつの計算をすると、一体どのような結果が得られるのでしょうか。
上記の計算で得られたふたつの変数を、Cells プロパティの引数として渡すと、次のように順番に処理していくことができます。

634.gif

左上から、右方向に、一段ずつ処理していくことができるのですね。
今回のブロック崩しでは、ブロックにラベルコントロールを使っていましたよね。そして、そのラベルは名前が連番になっていたのを憶えていますか? つまり、このセルの処理順序は、ラベルの名前の連番と全く同じ順序になっていたですね。

さて、次です。
続いては、上記のようにして得られたセルの参照を使って、順番にセルの値を調べていきます。このとき、セルの値が 0 より大きかった場合だけ、ブロック管理構造体にデータを取得していきます。
ブロックの横位置を表す X 、同様に縦位置を表す Y にそれぞれ計算した座標を入れておき、ブロックが見えているかどうかを表す Vis には True を入れます。
最後に、ユーザーフォーム上の実際のラベルを見えるようにするために、ラベルのVisible プロパティに True を設定して完了です。

ちなみに、セルの値が 0 だった場合には、逆にブロックが見えないようにしておく必要があります。ブロック管理構造体の要素 Vis と、ラベルの Visible プロパティには、いずれも False を設定しておきます。

ちょっと紛らわしいですが、落ち着いて考えましょう。
このプロシージャでポイントなるのは、なんといってもセルの参照を得るために行われる計算です。ここがちょっと複雑なのですが、あとは難しいことはしていません。ゆっくりじっくり考えましょう。


■ユーザーフォーム側の準備

さて、前回の講座内容と、今回の内容で、初期化の部分は解説できました。
ボールやバーの初期化処理と、ブロックの初期化処理のふたつが完成しましたので、これをユーザーフォームの起動と同時に呼び出せるようにしておきます。

ユーザーフォームの Initialize イベントを使って、ふたつの初期化処理が呼び出されるようにしておきましょう。以下のプロシージャは、フォームモジュールに記述するコードです。標準モジュールではないので間違えないようにしましょうね。

Private Sub UserForm_Initialize()

    Init
    
    Init_Block 1
    
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)

    End
    
End Sub

ここでは特に難しいことはしていませんが、注意するべきは Init_Block の呼び出し部分。このプロシージャはステージ数を引数として受け取るように設計されていましたよね。とりあえず現状はここで 1 を指定して、ステージ 1 が読み込まれるようにしておきます。

このようにフォームモジュールに記述したあとで、ユーザーフォームを起動してみてください。すると、ワークシートに設計したとおりのブロック配置で、ユーザーフォームが起動するはずです。

ステージの設計図を作ったシートに、複数のステージを作っておいて引数を変更すると、きちんと別のステージも読み込まれるはずです。興味のある人はやってみてもいいでしょう。

630.gif


■まとめ

今回は結構長くなりましたが、どうでしょうか。
ステージを読み込む部分がちょっと複雑でしたが、これは一度作ってしまえばかなり汎用的に使えるプロシージャなので、がんばって仕組みを理解しておいてくださいね。

最後に画像で紹介したように、同じレイアウトのステージ設計図を作っておけば、引数を変更してプロシージャを呼び出すだけで、簡単に複数ステージを扱うことができます。また、前回のボールやバーの初期化処理と、今回のブロックの初期化処理を分けて作成してあるおかげで、初期化を別々のタイミングで管理することが可能になっています。このことは、ブロック崩しゲームの作成が進んでいくほど重要になってきます。

今回の講座では、ワークシートとプログラムの連携、そしてセルの参照を行う計算の仕組み、これを理解しておけば大丈夫です。今回はちょっと複雑だったので、サンプルをダウンロードできるようにしておきます。どうしても計算の仕組みがよくわからない場合は、サンプルを実際に動かしながら、それを参考にして考えるのもいいでしょう。とにかく、焦りは禁物です。


サンプルダウンロード ⇒ コチラよりダウンロードできます。(別館)



■格言

ワークシートをうまく活用
複数のステージに対応できるように設計
セルの参照は複雑だけど落ち着いて


次回はメインループなどの根幹処理を作りましょう。








メールフォーム

影斬に物申すという方はこちら

名前 :
メール:
件名 :
本文 :

可能な限り要望には応えますが、必ず返信や回答ができることを、保障するものではありません。
ご了承ください。

Chapters

コンテンツ一覧


■Chapter 一覧■
    全てのChapterの一覧です。
    直接アクセスしたい方はこちらをご利用下さい。

    Chapter.1 [ 知っておくべき心得 ]
    Chapter.2 [ Excelってなんだろう ]
    Chapter.3 [ Excelの基本画面 ]
    Chapter.4 [ VBAとは? ]
    Chapter.5 [ モジュールについて ]
    Chapter.6 [ 変数 ]
    Chapter.7 [ 変数の型と宣言 ]
    Chapter.8 [ プロシージャとスコープ ]
    Chapter.9 [ ゲームつくる様々な手法 ]
    Chapter.10 [ ユーザーフォーム ]
    Chapter.11 [ プロパティウィンドウ ]
    Chapter.12 [ 乱数 ]
    Chapter.13 [ 条件分岐 ]
    Chapter.14 [ ゲーム画面のデザイン ]
    Chapter.15 [ コード記述の基本作法 ]
    Chapter.16 [ じゃんけんゲーム:1 名前をつける ]
    Chapter.17 [ じゃんけんゲーム:2 フォームの起動 ]
    Chapter.18 [ じゃんけんゲーム:3 乱数の種 ]
    Chapter.19 [ じゃんけんゲーム:4 イベント ]
    Chapter.20 [ じゃんけんゲーム:5 引数 ]
    Chapter.21 [ じゃんけんゲーム:6 役判定 ]
    Chapter.22 [ じゃんけんゲーム:7 予測と制限 ]
    Chapter.23 [ Withステートメント ]
    Chapter.24 [ 画像を表示させる ]
    Chapter.25 [ 画像表示の発展形 ]
    Chapter.26 [ 繰り返し処理 For文 ]
    Chapter.27 [ 繰り返し処理 Do~Loop文 ]
    Chapter.28 [ Exitステートメント ]
    Chapter.29 [ フォーム上の位置情報 ]
    Chapter.30 [ API基礎知識 ]
    Chapter.31 [ API補足知識 ]
    Chapter.32 [ メインループを考える ]
    Chapter.33 [ 同期処理の概念 ]
    Chapter.34 [ 移動処理その1:画面設定と考え方 ]
    Chapter.35 [ 移動処理その2:DoEvents ]
    Chapter.36 [ 移動処理その3:キー入力判定API ]
    Chapter.37 [ 条件分岐のさらなる探求 Select Case ]
    Chapter.38 [ アニメーション ]
    Chapter.39 [ 配列変数 ]
    Chapter.40 [ ゲームの初期化 ]
    Chapter.41 [ シューティングゲーム1:ゲーム設計 ]
    Chapter.42 [ シューティングゲーム2:メインプロセス ]
    Chapter.43 [ シューティングゲーム3:構造体 ]
    Chapter.44 [ シューティングゲーム4:定数 ]
    Chapter.45 [ シューティングゲーム5:プレイヤーキャラクター ]
    Chapter.46 [ シューティングゲーム6:ショットを撃つ① ]
    Chapter.47 [ シューティングゲーム7:ショットを撃つ② ]
    Chapter.48 [ シューティングゲーム8:Mod演算子の活用 ]
    Chapter.49 [ シューティングゲーム9:敵キャラクター登場 ]
    Chapter.50 [ シューティングゲーム10:衝突判定 ]
    Chapter.51 [ シューティングゲーム11:衝突の実体 ]
    Chapter.52 [ シューティングゲーム12:敵の攻撃 ]
    Chapter.53 [ シューティングゲーム13:爆発エフェクト ]
    Chapter.54 [ シューティングゲーム14:残機数表示① ]
    Chapter.55 [ シューティングゲーム15:残機数表示② ]
    Chapter.56 [ シューティングゲーム16:スコアの表示 ]
    Chapter.57 [ シューティングゲーム17:タイトル画面 ]
    Chapter.58 [ シューティングゲーム18:ボスキャラクター ]
    Chapter.59 [ シューティングゲーム19:最後の仕上げへ ]
    Chapter.60 [ シューティングゲーム20:いよいよ完成STG ]
    Chapter.61 [ カードゲームで使えるめくり効果 ]
    Chapter.62 [ ラジアンと角度 ]
    Chapter.63 [ ラジアンの活用:円運動 ]
    Chapter.64 [ ラジアンの活用:任意の角度へ移動する ]
    Chapter.65 [ APIによるサウンド再生:基礎 ]
    Chapter.66 [ APIによるサウンド再生:MIDIと多重再生 ]
    Chapter.67 [ APIによるサウンド再生:MCIコマンドとループ再生 ]
    Chapter.68 [ Function プロシージャ ]
    Chapter.69 [ 値渡しと参照渡し ]
    Chapter.70 [ デバッグ1:イミディエイトウィンドウ ]
    Chapter.71 [ デバッグ2:ローカルウィンドウ ]
    Chapter.72 [ デバッグ3:コード実行の中断 ]
    Chapter.73 [ オブジェクトってなんだ ]
    Chapter.74 [ プロパティ・メソッド・イベント ]
    Chapter.75 [ オブジェクト変数 ]
    Chapter.76 [ オブジェクトとコレクション ]
    Chapter.77 [ 特殊な繰り返し:For Each ]
    Chapter.78 [ エラー処理 ]
    Chapter.79 [ On Error と GoTo文 ]
    Chapter.80 [ Resumeステートメント ]
    Chapter.81 [ バイトとビット ]
    Chapter.82 [ ウィンドウメッセージとイベント ]
    Chapter.83 [ 文字列の基礎 ]
    Chapter.84 [ 文字列操作① ]
    Chapter.85 [ 文字列操作② ]
    Chapter.86 [ タイピングゲーム1:仕様を決める ]
    Chapter.87 [ タイピングゲーム2:キー入力検知 ]
    Chapter.88 [ タイピングゲーム3:文字列照合 ]
    Chapter.89 [ タイピングゲーム4:判定関数 ]
    Chapter.90 [ タイピングゲーム5:ゲーム画面設計 ]
    Chapter.91 [ タイピングゲーム6:問題文のソート ]
    Chapter.92 [ タイピングゲーム7:動的配列 ]
    Chapter.93 [ タイピングゲーム8:キーダウンイベント ]
    Chapter.94 [ タイピングゲーム9:正打数の表示 ]
    Chapter.95 [ タイピングゲーム10:タイムの表示 ]
    Chapter.96 [ クリックゲーム1:イベントの種類 ]
    Chapter.97 [ クリックゲーム2:画面設計 ]
    Chapter.98 [ クリックゲーム3:クリック座標検知 ]
    Chapter.99 [ クリックゲーム4:キャラクター準備 ]
    Chapter.100 [ クリックゲーム5:キャラクターの配置 ]
    Chapter.101 [ クリックゲーム6:キャラクター移動とNot演算子 ]
    Chapter.102 [ クリックゲーム7:クリックのヒット判定 ]
    Chapter.103 [ クリックゲーム8:ヒットマークエフェクト ]
    Chapter.104 [ クリックゲーム9:サウンド処理の実装 ]
    Chapter.105 [ クリックゲーム10:マウスカーソルの変更 ]
    Chapter.106 [ ブロック崩しゲーム1:仕様と概要を決める ]
    Chapter.107 [ ブロック崩しゲーム2:基本概念の確認 ]
    Chapter.108 [ ブロック崩しゲーム3:ベクトルとは ]
    Chapter.109 [ ブロック崩しゲーム4:変数や定数の宣言 ]
    Chapter.110 [ ブロック崩しゲーム5:初期化処理の実装 ]
    Chapter.111 [ ブロック崩しゲーム6:ブロックの配置 ]
    Chapter.112 [ ブロック崩しゲーム7:根幹処理とバーの処理 ]
    Chapter.113 [ ブロック崩しゲーム8:線分と線分の交差を判定 ]
    Chapter.114 [ ブロック崩しゲーム9:線分同士の交点 ]
    Chapter.115 [ ブロック崩しゲーム10:ボールの処理 ]
    Chapter.116 [ ブロック崩しゲーム11:最終調整して完成へ ]
    Chapter.117 [ テキストファイル操作基礎 ]
    Chapter.118 [ テキストファイル操作:読み込み編 ]
    Chapter.119 [ テキストファイル操作:CSV読み込み編 ]
    Chapter.120 [ テキストファイル操作:様々な読込編 ]
    Chapter.121 [ テキストファイル操作:バイナリ編 ]
    Chapter.122 [ テキストファイル操作:暗号化編 ]
    Chapter.123 [ テキストファイル操作:復号化編 ]
    Chapter.124 [ クラスモジュールとは ]
    Chapter.125 [ クラスモジュール:メソッド編 ]
    Chapter.126 [ クラスモジュール:プロパティ編 ]
    Chapter.127 [ クラスモジュール:イベント拡張編 ]
    Chapter.128 [ クラスモジュール:イベント自作編 ]
    Chapter.129 [ APIによる描画処理1:ハンドル ]
    Chapter.130 [ APIによる描画処理2:デバイスコンテキスト ]
    Chapter.131 [ APIによる描画処理3:ペン オブジェクト ]
    Chapter.132 [ APIによる描画処理4:ブラシ オブジェクト ]
    Chapter.133 [ APIによる描画処理5:図形描画準備編 ]
    Chapter.134 [ APIによる描画処理6:図形描画実践編 ]
    Chapter.135 [ APIによる描画処理7:画像描画の仕組み編 ]
    Chapter.136 [ APIによる描画処理8:ビットブロック転送編 ]
    Chapter.137 [ APIによる描画処理9:ラスタオペレーション ]
    Chapter.138 [ APIによる描画処理10:マスク描画 概念編 ]
    Chapter.139 [ APIによる描画処理11:マスク描画 実践編 ]


    コードやVBAに関する質問などはサポート掲示板(別館)までお気軽にどうぞ。




fc2 seotool Excel VBA ゲーム プログラミング 講座

Counter

twitter


Shadow BBS - 影掲示板

VBA 関連書籍



上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。