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

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

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



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


スポンサーサイト

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






Chapter.41 [ シューティングゲーム1:ゲーム設計 ]

■いよいよ本格始動

実践編はメインテーマとして『動きのあるゲーム』と題して、ここまで進めてきました。
キャラクターの移動処理や、アニメーションの仕方について、順番に解説してきましたね。ここまでの講座をしっかり進めてきていれば、もうすぐにでもゲームを作ることができると思います。

ゲームを作成するうえで大切なことは、習得した技術をどう使うか、どう応用するか、どう発展させるか、そういったことなのではないのかと、私は思います。
技術だけがあっても、知識だけがあっても、それだけでは面白いゲームは生まれません。たくさんの豊富な知識が選択肢を広げることは確かですが、それが必ず面白いゲームにつながるとは限らないのです。

たとえ、限られた技術や知識であっても、その使い方次第で、面白いゲームや素晴らしいゲームはいくらでも作れます。皆さんは、是非そのことを忘れないでください。


と、なんとなくカッコイイことを冒頭から書いてしまいましたが、実際の話、お手本が必要なのは事実です。どのような分野でも、最初は模倣から始まります。独自の物を表現できるようになるには、ひととおり既存のものを真似つくしてからです。
私の今まで培ってきた技術の一端をここで紹介することにより、誰かがそれを模倣する。そして、いつかは自分なりの表現を模索し始める。なんと素晴らしいことなのでしょう。私が多くの先人達を真似してきたように、私自身が誰かの役に立てるなら、これほどうれしいことはありません。

そんなわけで今回からは、実際にシューティングゲームを作成してみることにします。

もちろん、ひとくちにシューティングゲームといっても、様々な表現法やプログラミング法があるでしょう。
ここで紹介できるのは、私なりのスタンダードでしかありません。それをどう自分の中で活かしていくのかは皆さん次第です。とりあえずは講座の内容に沿って、作成してみてくださいね。講座に沿ってひとつの作品を仕上げたときには、皆さんの中に何かが誕生していることを祈ります。


■大事な大事な最初の展望

ゲームを作成するときには、まず、どんなものを作るのか、おおよその道筋は立てておいたほうがいいです。
理由は色々ありますが、一番大きな理由としては、『モチベーションの維持』が挙げられます。

プログラミングという作業は、実はかなり地味で根気のいる作業です。それこそ、小説を書いていくようなもんです。

夏休みの宿題で、読書感想文に四苦八苦したという経験は誰しもあると思います。まぁ、中には得意な人もいるんでしょうが。
読書感想文などの、非常に抽象的なテーマで何かを表現しなければならないものは、えてして何を書いていいのやら、それがまずわからない。ですから、どうしても筆が進まないのですね。ゲームの作成もこれと似たようなことが言えます。

完成形がしっかりイメージできていないうちに、適当に作り始めてしまったものは、大抵適当なままで終わります。むしろ、完成しないで終わってしまうことのほうが多いでしょうね。
できあがったときの作品のイメージがないので、『完成した作品を、早く自分の目で確かめたい』という欲求が沸きにくいのです。

まずは、自分の納得のいくテーマや、イメージをしっかり固めることが大事です。そうする事によってどんなゲームを作りたいのか、おのずと見えてきます。今回は講座というものの性質上、ある程度は私が提示していくことになります。ですが、自分の中での具体的な目標はしっかり固めておきましょう。


さて、前置きが異常に長くなりました。そろそろ具体的な内容に移りましょう。

今回作成するシューティングゲームは、次のような完成形を目指します。

ゲーム画面はユーザーフォームで設計
縦スクロールシューティングゲーム
クリア条件はボスの撃破
3回自機が破壊されたらゲームオーバー
攻撃手段はショットのみ

とりあえずおおまかな概要はこんな感じで。


■方向性が決まったら基盤を作る

さて、大体の方向性が決まったところで、次にやるべきことは何でしょうか。
次にやるべきは『基盤づくり』です。じつはこれが非常に大事。

この講座では、何度も基本が大事という話をしてきましたね。基本が理解できないうちは、発展した技術を理解することはできないのですね。
これと同じように、ゲームの作成では基盤がとても大事になります。基盤がしっかりしていないと、先に進むほどボロが出てきます。
さらに、基盤ができていない場合には、大抵問題が発生したときの修正も難しいことが多く、ゼロから作り直し……なんて事にもなりかねません。

まずは画面の設計。それから軸となるプロシージャの作成。とりあえずはここを整備することが基盤づくりの第一です。今回はまず、ゲーム画面の準備から始めましょう。

ゲームの画面にはユーザーフォームを使います。新しくユーザーフォームを挿入し、次のようにコントロールを配置します。各コントロールの名前、大きさ等は、必ずしもこれである必要はないのですが、講座に沿って進めていくなら、全く同じにしておかないとコードの実行時に不具合が出る可能性があります。もし独自の変更を行う場合には、ご自分の判断で、適宜行ってください。

210.gif

ちなみにこれは最低限の仕様です。
今後、講座を進めていく中で、必要なものは随時追加していきます。

とりあえず今の段階で設置したコントロールについて、少し解説しましょう。


①ラベル:Lab_Score
このラベルコントロールは、名前から見てわかるとおり、スコアを表示するのに使います。
ラベルは数値や文字などを表示するのに適したコントロールです。スコアのような数値の表示には、用途として最も適しているでしょう。
このラベルで注目すべきは『Fontプロパティ』と『TextAlignプロパティ』の設定です。

Fontプロパティ』は、ワークシートなどでフォントを変更するとき同様、フォントの設定ダイアログを使って、コントロールのフォントを設定するプロパティです。

212.gif

今回の場合は、少しだけ文字を大きくしています。規定では9に設定されていますが、今回はサイズを11に設定。さらに太字にして、少しだけ目立つようにしています。

TextAlignプロパティ』は、文字の表示を左寄せや右寄せに設定できるプロパティです。中央揃えもできます。
数値は基本的に右寄せのほうが見やすいですし一般的です。ですから今回はここに『fmTextAlignRight』を設定して、右寄せで文字列を表示するように設定しています。


②フレーム:Frame
フレームは、ちょっと特殊なコントロールです。
ユーザーフォームの中に、独立した別の空間を作り出す、みたいなことができます。

例えば、Topプロパティとは普通、ユーザーフォーム上での縦位置を表すプロパティでしたね。ユーザーフォーム上にあるコントロールのTopプロパティが0(ゼロ)のときは、ユーザーフォームの上端にくっつくいたような感じになります。

しかし、フレームを配置し、その中に別のコントロールを配置すると、そのコントロールはフレームを基準とした位置情報で管理されるようになります。
もし中に置かれたコントロールのTopプロパティを0に設定したなら、そのコントロールはフレームの上端にくっついたようになります。
さらに数値を小さくしていくとどうなるでしょう。こういった場合、理論上はフレームからはみ出してしまうはずなんですが、あくまでもフレームの中で管理されているので、外にはみ出してしまうことはありません。

214.gif

このような仕組みをうまく活用すると、フレーム自体がキャラクターなどを表示する空間として使えます。スコアやボタンなどと、視覚的に切り離して処理することができるわけですね。

実際にキャラクターが表示される空間として、このフレームを使います。ですから、前回の講座で書いたように、適切な大きさにあらかじめ整形しておきます。
横幅を表す『Widthプロパティ』と、縦幅を表す『Heightプロパティ』に、今回はそれぞれ『200』を設定します。
今回のゲームはこの『200×200』の空間で処理することになります。

このフレームコントロールで、規定とは違う設定をしているプロパティはあと2つあります(位置情報は除く)。ちょっと設定することが多くて大変ですががんばってください。

まずは『Captionプロパティ』です。フレームは規定でCaptionプロパティに『Frame1』などと文字が入っています。
このままでは、ゲーム画面として似つかわしくありません。第一このような文字はゲーム画面に必要なく、ただ邪魔なだけです。
そこで、Captionプロパティに設定されている文字は全て消してしまいます。そうすると外枠だけが残るので、画面としての用途に適した形になります。

215.gif

もうひとつ変更しているのは『BackColorプロパティ』です。灰色の空間と言うのもなんとなくおかしいので、今回はこのプロパティを変更して、フレームの中を黒く塗りつぶしています。
こうすることで、キャラクターが見やすくなり、存在感が引き立ちます。


③コマンドボタン:『Com_Start
これは何度も登場しているので、今更解説する必要がないかもしれませんね。
このコマンドボタンの役割はズバリ『スターター』です。いつもどおりですね。
このコマンドボタンから、ゲームのメインループを呼び出すようにしておけば、プレイヤーが任意のタイミングでゲームをスタートできるわけです。

表示される文字が、規定では『CommandButton1』になっていますので、Captionプロパティを変更して、『START』としています。これでスタートボタンのような役割を果たすことが、プレイヤーにも直感的にわかりやすくなるでしょう。


■ゲーム画面の設定まとめ

各コントロールには、それぞれ膨大な量のプロパティが存在します。
しかし、実際にはほとんどが規定値のままで問題ありません。今回説明したいくつかのプロパティを適切に変更しておけば、大きな問題はないはずです。
焦らず、ひとつひとつ設定しておいてください。

それと、この段階では、TopプロパティやLeftプロパティなどの、位置情報を管理するプロパティは適当でもいいです。ある程度の位置関係は見据えておくべきですが、あとでいくらでも修正できるので、それほど厳密に設定しておかなくてもいいでしょう。

先ほども言いましたが、必要なコントロールは講座を進めながら適宜追加していきます。細かな設定方法なども、その都度説明したほうがわかりやすいと思います。
今はまだ素っ気無い画面でしかありませんが、基本的な部分に関わることですから、もれなくしっかり設定を行っておきましょう。


■格言

あらかじめ作品のイメージをしっかり決める
基盤となる部分から地道に作成
コントロールの設定はしっかりと


のっけからやること盛りだくさんでした。お疲れ様でした。
スポンサーサイト






Chapter.42 [ シューティングゲーム2:メインプロセス ]

■軸となる処理

前回から始まったシューティングゲーム作成講座。
第1回目から、なにかとやることが多くて大変でしたね。中には、自分には無理だと、諦めてしまった人もいるかもしれません。

しかし、おどすわけではないのですが、前回の内容はまだ、比較的簡単なことしかやってません。というより、ゆっくりやれば決して難しくはない、という程度のことしかやっていないのです。
焦る必要はありません。なぜなら、一度で全てを理解しなくたっていいんですから。仕事で追い込まれながらプログラミングしてるわけじゃあるまいし、気長に、わかるところからやっていけばいいんです。

諦めてしまったら全てが終わりです。少なくとも、私のほうではキチンと動作確認をして、大丈夫だったコードだけを掲載していますから、うまくいかないときは見比べれば必ず何か原因が見つかるはずです。


さて、今回の内容は『メインプロセス』と題しました。
以前の講座で、メインループについて解説しましたが、要するにそれと同じようなものです。
今回はキチンとシューティングゲームを作るために、その軸となる、メインのコードを考えます。ここがコードの全体像を設計する上で、一番大切な部分といっても過言ではありません。

しかし、メインプロセスとは一体どういうことなのか、いきなり想像するのは難しいと思います。まずはメインプロセスとは何なのか、そこから解説していきましょう。


■コードの部品化

今までの講座では、いちいち説明こそしませんでしたが、基本的に『プロシージャの部品化』という手法でコードを書いてきました。

例えば、以前解説したキャラクターの移動処理や、アニメーション処理などは、全て『コマンドボタンから別のプロシージャを呼び出す』という手順で行っていましたね。
コマンドボタンをクリックすると、そこから別のプロシージャが呼び出される、というヤツです。
しかし、実際には、このような回りくどい方法を使わなくても、コマンドボタンのクリックイベントそのもので、移動処理やアニメーション処理を行うこともできたのです。コマンドボタンのクリックイベントに、全ての処理をまとめて記述しておけば、それでキチンと同じことが実現できてしまいます。

それならば、なぜ、わざわざ別のプロシージャを用意して、それを呼び出すなんて面倒なことをしたのか。これは、今回のメインプロセスという考え方に、スムーズに移行できるようにしたかったからです。


ゲームのプログラミングというのは、ほとんどの場合、きわめて複雑な処理を求められます。しかもそのときどきの状況によって、いろいろなことを想定しながら処理していかなくてはいけません(ゲームに限ったことではないですけどね)。
例えばロールプレイングゲームなら、今現在はタイトル画面なのか、それともマップ画面なのか、はたまた戦闘中の画面なのか。もしかしたらアイテムを使おうとしているのか、魔法を唱えようとしているのか、武器の装備を変更しようとしているのか……。ゲーム中の状況なんて、挙げていったらキリがありませんね。

こういった、状況によって様々に変化する処理を、全て同じプロシージャとして記述していくのは、絶対的に技術面で難しいのが想像できますよ。しかもコードが膨大な量になるため見づらい、読みづらい、わかりづらい。まぁ要するにいいことがないわけです。

しかし、状況によって異なる処理を、それぞれ個別のプロシージャに分けておいたらどうでしょうか。

タイトル画面を表示する専用のプロシージャがあるなら、それだけ呼び出していればタイトル画面はOKですね。同様に、マップ画面専用の処理だけを記述したプロシージャを用意しておけば、それだけでマップ画面は問題なし。
アイテムを使用するときのためのプロシージャが用意してあれば、ゲームの進行がどのような状態であっても、それだけ呼び出せばいつでもアイテムの使用ができるはずですね。

これが『コードの部品化』つまりは『プロシージャの部品化』です。
複雑な処理が求められる場合でも、それぞれが個別に管理されていれば、プログラムはそれを呼び出すかどうかの判断だけで、キチンと動くようになります。プログラミングをする我々から見ても、これなら非常にわかりやすいですね。
バグが出てしまった場合にも、特定の状況ごとに分けて管理しているので、どこでバグが発生しているのか一目瞭然です。修正する場所はどこなのか、すぐに察しがつきます。
アイテムを使おうとして不具合が発生したなら、アイテムの処理を担当しているプロシージャが怪しいのがすぐにわかるというわけです。

今回のテーマであるメインプロセスとは、これらの部品化されたプロシージャを、いつ、どうやって呼び出すかを管理するプロシージャを指します。人間で言えば、脳にあたる部分ですね。

人間がテーブルの上にあるりんごを取りたいと思ったら、脳が指令をだして手を動かすでしょう。これと同じように、ゲーム中でなにかをしたいと思ったら、このメインプロセスから目的の処理を呼び出すようにするのです。


■シューティングゲームの構造化

シューティングゲームを作成する場合でも、メインプロセスから部品化されたコードを呼び出す、という設計は非常に有利です。なぜなら、シューティングゲームにも、実に様々な状況があるからです。

とりあえず、簡単なところだけ列挙して一覧にしてみましょう。

ゲーム設定の初期化処理
ゲームが終了されようとしているかどうか判断する処理
タイトル画面の処理
プレイヤーの操作する機体の処理
敵ザコキャラクターの処理
プレイヤーの撃った弾の処理
敵キャラクターの撃った弾の処理
ボスキャラクターの処理
敵が出現するかどうか判断する処理
爆発などのエフェクトの処理
スコアの更新などその他の処理
ゲームオーバーやクリアなどの判断処理
想定外の状況、つまりエラーが出た場合の処理

ほとんど最低限のことだけですが、それでもこれぐらいはあります。なんとまぁ……。

もっとこった演出をしようと思ったら、その分だけ処理の種類が増えます。例えばパワーアップアイテムとかを設けるなら、アイテムの処理も必要です。スコアランキング画面を取り入れたいなら、それも用意しなくてはいけないでしょう。単なるショットでの攻撃だけでなくボムも使いたいと思ったら、それも用意しなくちゃいけませんね。

とにかく、先ほど挙げたのは、ほとんど最低限度の処理だけです。それでもこれだけあるんですね。ちょっと気が遠くなってしまいますか? 大丈夫でしょうか……。心配です。

でも、ここで諦めないでください。
これから先、講座を進めていく中で、上であげた処理をひとつひとつ解説していくつもりです。1回の講座で、できる限り焦点を絞って解説していきます。今までもそうでしたが、焦らずひとつずつこなしていけば、最後には必ず作品が仕上がります。最終的には絶対できるんです。ですからここで簡単に諦めたりせず、がんばってついてきてほしいと思います。


■メインプロセスの実体

それでは、具体的なメインプロセスをコードにしてみましょう。

大切なことは、以前解説したメインループの構造をよく理解していることです。それさえわかっていれば、たいしたことはありません。
それでは早速下のコードを見てください。

Sub Main() 'メインループとなるプロシージャ
    Dim Flg As Boolean
    Dim Stm As Long
    ゲーム設定の初期化処理
    Flg = False
    Do Until Flg
        Stm = GetTickCount
        Select Case 現在のモード
            Case タイトル画面モード
                タイトル画面の処理
            Case ゲームのプレイ中モード
                敵出現判断処理
                プレイヤーの処理
                敵ザコキャラの処理
                敵ボスキャラの処理
                プレイヤーのショット処理
                敵キャラのショット処理
                エフェクトなどの処理
                スコアの更新処理
            Case プレイヤー死亡時モード
                ゲームオーバー処理
            Case ゲームクリア時モード
                ゲームクリア処理
        End Select
        DoEvents
        Do
            Call Sleep(1)
        Loop Until GetTickCount - Stm > 30
        ゲームが終了されようとしているかどうかの判断処理
        エラー時の処理
    Loop
End Sub

うおおぉっ! なんだこれはー!!

という心の叫びは置いといて、とりあえずこのような感じにやっていきますよ、というところだけ理解してください。見た目の複雑さにビビッてはいけません。

上に挙げたコードが、メインプロセスとなるプロシージャです。このプロシージャの名前はそのものズバリ『Main』となっています。

まず、コードが実行されると、始めに変数などのリセットを行う初期化処理があります。ここで、ゲームの設定を一度全て白紙に戻して、繰り返しゲームをプレイしても環境が変化しないように配慮します。
前回のスコアなどが残っていると、おかしなことになりますもんね。そのための初期化処理です。

次に、Do文を使ったメインループが始まります。
その中で、現在のモードとなっている部分がありますね。これは現在のゲームの進行状況を表しています。進行状況に応じて、Select Case文で処理を分岐するようにしているわけです。

もし、現在のモードがタイトル画面なら、その下のタイトル処理に進みますし、ゲームのプレイ中だったらそれに応じた処理に移ります。
もし状況が変化したときは、この現在のモードだけを変更すれば、あとは勝手に分岐していくという寸法です。

最後のほうでは、ゲームの終了処理が出てきていますね。
プレイヤーがどんな状態の時にゲームを終了するかはわかりません。終了したいときに、キチンと処理が終了するようにするために、これはしっかりやっておかなくてはいけません。

さらに一番最後にはエラーの処理を設けています。エラーが発生したときに、思わぬ事態を招かぬように、ここでしっかり設定をしておきます。


まずコードの全体像を把握できていれば、今の段階では問題ありません。メインプロセスとなる今回のコードから、状況に応じて、それぞれの処理を呼び出している、という仕組みが重要です。
このような構造のプログラムは、思わぬ不具合が発生したときに、その原因が探しやすいうえに、修正も簡単です。エラーが起きているプロシージャだけを修正すれば、ほかに影響が出にくいからですね。

さらに、機能の拡張という面でも優れています。
先ほども書いたように、もしアイテムが出現するように修正したいと思ったときには、あとからそれだけを管理するプロシージャを用意して、メインプロセスから呼び出されるように追加するだけでいいのです。


見た目はともかく、構造は比較的単純です。
時間がかかってもいいので、しっかり理解しておいてください。

次回からは、これらの『部品化されたコード』をひとつひとつ用意していきます。最終的には、上のメインプロセスからそれらが適宜呼び出され、ゲームとして完成します。
道のりは確かに長いですが、最後には必ず出来上がるはずです。がんばりましょう。私もがんばります。


■格言

メインプロセスは人間でいう脳
部品化されたプロシージャを適宜呼び出す


説明するのが難しい内容でした。大丈夫かなぁ……。






Chapter.43 [ シューティングゲーム3:構造体 ]

■ユーザー定義型変数

前回、プロシージャの部品化ということを解説しました。その利点も、なんとなく理解できたのではないかと思います。

プロシージャを部品化しておくと、メンテナンス性、可読性、柔軟性に優れたプログラムが出来上がります。軸となるメインプロセスから、目的の動作を個別に呼び出すことで、自由な構成、デバッグしやすい環境を整えることができるのでしたね。

そして今回からは、シューティングゲームの部品となる、様々なプロシージャを用意していきます。それらの全ての部品が完成したなら、あとはちょっと手を加えるだけで、シューティングゲームが完成します。なんだかプラモデルみたいですね。

まず最初は、プレイヤーが操作する『自機キャラクター』を管理するプロシージャを作成したいと思っています。この部分を先に作ってしまえば、なんとなくそれだけでもゲームっぽい感じがします。この部分が出来上がっただけでも、ゲームを作っているという実感が持てるのではないかと思います。


さて、プレイヤーキャラクターの処理について、その実体を解説する前に、とても有用な知識を身につけていただきたいと思います。ゲームのプログラミングにおいては、これがとても役に立つんです。私はこの仕組みがないと、もう開発ができないほど、頻繁に使っています。
それは『ユーザー定義型変数』と呼ばれている仕組みです。全く同じ仕組みのことを、C言語などでは構造体と呼びます。便宜上、ここから先の講座内では、この仕組みを構造体と呼ばせていただきます。


■構造体ってなんだ?

構造体というのは、先ほども書いたように正式には『ユーザー定義型変数』と呼ばれているものです。これは一体何なのでしょうか。

例えばかの有名な『インベーダーゲーム』を思い浮かべてみてください。(正確にはスペースインベーダーと言ったらいいのかな?)
インベーダーゲームを知らないという人はいないと思います。もし、知らない人がいたならすいません。最近はブラウザ上で動作するインベーダーもあるようです。余談ですが。

さて、インベーダーゲームでは、たくさんの敵キャラクターが出現します。彼らは、その姿かたちが微妙に違っていますね。それに、厳密に言えば表示されている位置も、1体1体違いますね。

1体目のインベーダー:横1、縦1の場所にいて、見た目はA
2体目のインベーダー:横2、縦1の場所にいて、見た目はB
3体目のインベーダー:横3、縦1の場所にいて、見た目はC

    :
    :

55体目のインベーダー:横11、縦5の場所にいて、見た目はE

このように、それぞれのキャラクターは、それぞれに全く異なる個別のパラメーターを持っています。ひとつとして、そのパラメーターが完全に一致するキャラクターはないわけです。
これをプラグラムするということは、1体1体のキャラクターの情報を、個別の変数に確保しておく必要がありますよね。完全に同じということがないのですから、使い回しするわけにはいきません。

これを普通に考えると、以前解説した配列変数を使いたくなります。

    Dim X(敵キャラ数) As Long '横位置管理用
    Dim Y(敵キャラ数) As Long '縦位置管理用
    Dim L(敵キャラ数) As Long '見た目管理用

こうしておけば、なんとなく効率よく管理できそうな気がします。

もちろん、これは間違いではありません。このように配列で管理することで、For文などの繰り返し処理とも相性がよくなります。

しかし、私はこんなとき、次のように記述して、パラメーターを管理するでしょう。

Type Chara
    X As Long
    Y As Long
    L As Long
End Type

Dim Enemy(敵キャラ数) As Chara

さて、どうでしょう。なんとなく意味がわかりますか?

Type』というキーワードが出てきましたね。これがユーザー定義型変数、つまり構造体を宣言するためのキーワードです。

Typeというキーワードに続けて、その構造体の名前を記述します。今回の場合なら『Chara』がそれにあたります。そして、必要な要素を続けて宣言していきます。このときは通常の変数宣言と同じルールで記述すればOKです。
例えば文字列型の変数を構造体に含めたい場合には、『~As String』とすればいいわけです。ブール型なら『~As Boolean』ですね。普通の変数宣言と全く同じです。
こうして全ての要素が定義できたら、『End Type』で構造体宣言を終わります。これで構造体の宣言は終わりです。

あとは、このように宣言した構造体を、そのまま変数の型として使い、変数を宣言します。今回の場合には『Enemy()』がそれにあたりますね。

この『Enemy()』という配列の変数の型は、『~ As Chara』となっています。そうです、先ほど宣言した構造体を、そのまま変数の型として使い、この配列は宣言されているのです。

このEnemyという配列は、内部的に、先ほどの構造体の要素を全て持っています。ですから、次のように、変数(または配列)名に続けてピリオドを打つと……

220.gif

おぉ! 便利。自動メンバ表示がかかるようになります。

さらに、普通の配列を使った場合と、構造体による配列を使った場合を比較してみると……

'構造体を宣言--------------------

Type Chara
    X As Long
    Y As Long
    L As Long
End Type

'構造体宣言はここまで------------

Sub Test()

    Dim Counter As Long
    Dim X(9) As Long
    Dim Y(9) As Long
    Dim L(9) As Long
    Dim Enemy(9) As Chara
    
    For Counter = 0 To 9
        
        '普通の配列
        X(Counter) = Counter
        Y(Counter) = Counter
        L(Counter) = Counter
        
        '構造体を使った配列
        With Enemy(Counter)
            .X = Counter
            .Y = Counter
            .L = Counter
        End With
    
    Next

End Sub

どうですか?
構造体を使った処理のほうが、スマートにわかりやすく記述できていると思いませんか?

実際には、これは好みの問題もあるのかもしれませんが、一般的には構造体を用いた記述のほうがわかりやすいと思います。
何より『Withステートメント』を利用した一括処理ができるというのが素敵です。素敵過ぎます!

コードの内容としては、ただ単にカウンタとなる変数の値を、繰り返し処理しながら代入しているだけですから、特に意味はありません。使い方をなんとなく理解していただきたかったので、簡単なサンプルを載せただけです。

構造体の宣言の仕方は、それほど難しくないと思います。
Typeで始まり、End Typeで終わる。それだけです。ただし構造体はモジュールの先頭で記述するようにしましょう。プロシージャの中で構造体を宣言することはできませんので注意してください。

構造体を理解していると、ゲームのプログラムを効率よく記述できます。是非、体得してください。


■本題に戻る

さて、構造体については理解できましたか? ユーザーフォームに配置するコントロールにはたくさんのプロパティがありますが、あれだって構造体と似たようなもんです。仕組みがわかってしまえば、きっとあなたにとってプラスになるはずです。

構造体の仕組みが理解できたら、本題に戻って、シューティングゲームのプレイヤーキャラクターについて考えてみましょう。

シューティングゲームでは、キャラクターが次のような要素を持っている必要があります。

表示位置を表す要素。
    ∟ 横位置と、縦位置。

大きさを表す要素。
    ∟ 横の幅と、縦の幅

生死を表す要素。
    ∟ 生きてるか死んでるか。

種類を表す要素。
    ∟ どんなタイプのキャラクターか。

汎用的に使える要素。
    ∟ 念のため予備として用意。

キャラクターは、その種類によって、大きさや見た目が異なるでしょうし、その時々によって、どの位置に表示されているかはわかりません。
それに、そもそも存在しているのか、いないのか。それがわからないと表示したり消したりできませんね。
なにか特別なことをやろうとしたときの為に、一応予備のデータを格納できるスペースも持っておいたほうがいいでしょう。例えばアニメーションさせたりする場合とかのためですね。

こうして考えてみると、今回作成するシューティングゲームでは、キャラクターを表す構造体は次のように宣言することができます。

'■キャラクター 構造体宣言
Type Chara
    X As Single    '横位置
    Y As Single    '縦位置
    W As Single    '横幅
    H As Single    '縦幅
    Lif As Long    'ライフ
    Typ As Long    'タイプ
    Par As Long    '汎用パラメーター
End Type

ちょっと行数が多くてビビりますね。
でも、始めにこのような構造体を宣言しておけば、あとは普段どおりの変数宣言の感覚で、これだけの変数を一度に宣言したのと同じ効果が得られるわけです。

これを踏まえて、プレイヤーが操作するキャラクターを、この構造体で宣言してみましょう。とはいっても、たった1行で済みます。

Dim Player As Chara

これだけです。先ほどの構造体宣言をキチンと行っていれば、これだけで、全ての要素を持ったPlayerという変数が宣言できてしまいます。

221.gif

かなりコードが記述しやすくなりましたね。


■格言

ユーザー定義型変数は構造体とも呼ばれる
たくさんの属性を同時に持つ変数が作れる
ゲームの作成には欠かせない


ロールプレイングゲームなどでも、構造体は非常に便利です。
HP、MP、力、技、魔力……とか、ひとつの構造体に集約できます。






Chapter.44 [ シューティングゲーム4:定数 ]

■プレイヤーキャラクターの処理……の前に

前回、プレイヤーキャラクターの処理を見てみるとか言っておきながら、結局、構造体の説明だけで終わってしまいました。意外と構造体の説明が長くなってしまって、途中から軌道修正してしまったからです。
今回はちゃんとプレイヤーキャラクターの処理を見ていきたいと思ったわけですが、準備をしなければならない部分が、実はまだ残っています。
それは、ゲームの根本的な部分の準備です。

実は、今回は実際のキャラクター処理を解説するつもりでした。ですが、このテキストを書いている時点で、いつもの2倍以上のドキュメントになりつつあります。これは一度に解説するべき量ではないな、と思ってしまいました。ちょっとボリュームがありすぎるのです。

そこで今回は、ちょっと私的にも残念なんですが、プレイヤーキャラクターの処理にスムーズに移行するために、最低限必要な根本的な処理をしっかり解説することにしました。
メインプロセスや、初期化処理など、あらかじめしっかり定義しておかないと後で困る部分が、シューティングゲームを作成するうえでどうしても出てきます。
逆に考えてみると、ここが適当では、全体が適当になってしまいまので、いい加減ではいけないと思ったのです。

そんなわけで、今回はシューティングゲームの根本部分を、しっかり固めていきたいと思います。ここで手抜きをすると、後でろくなことにならないので、焦る気持ちを抑えてがんばってください。

今回の内容は、前回解説した構造体の仕組みが理解できていないと、ちょっときつい部分が多いです。
もし、構造体ってなんじゃ? と思う方がいらっしゃったら、面倒でも、前回の講座をしっかり理解したうえで、チャレンジしてくださいね。


■ユーザーフォームへの追加項目

それでは早速とりかかりましょう。
今回作成するシューティングゲームは、プレイヤーキャラクターとして、次の画像を用意しました。以前の使いまわしでちょっと恐縮ですが、実はちょっとだけグラデーションなどが細かくなっているとかいないとか……。

Red_20080208121115.gif

この画像をフォーム上に用意したイメージコントロールにロードします。ここでポイントとなるのは、イメージコントロールを新たに追加する『場所』です。

以前の講座で今回のゲーム用の、ユーザーフォームの雛形を作りましたね。そこにはフレームコントロールが配置されていたはずです。そのフレームコントロールを、ゲームの画面として扱うと解説したのですが覚えているでしょうか。
今回追加するイメージコントロールは、キャラクターそのものとして使います。ですから、フレームコントロールの中に収まるように、あたらしくイメージコントロールを追加します。
それが次の画像。

230.gif

先ほどのキャラクター用画像の大きさは24×24ピクセルです。つまり、イメージコントロールの幅を定義する『WidthHeight』の2つのプロパティは『18』となります。これはピクセルとポイントという、単位の相違から生まれる変更点でしたね。
そのほかにも、透過処理設定や、枠線の非表示設定など、やるべきことをしっかりやっておきましょう。(詳しくは以前の解説を参照→Chapter24Chapter25)

ここまでうまくいったら、このプレイヤーキャラクター用のイメージコントロールの、名前を変更しておきます。今回は『Player』という名前にすることにします。
こうしておくことで、あとでプロシージャからこのコントロールを参照するときにわかりやすくなります。

231.gif


■コーディング

さて、早速実際のコーディングに入ります。いよいよ本格的にゲームを作成し始める段階に突入です。


まず、標準モジュールを挿入します。
そして、モジュールの先頭部分で宣言しなければならない『API宣言』や『構造体宣言』を準備しましょう。

モジュールの先頭に記述
'◆API 宣言
Declare Function GetTickCount Lib "kernel32" () As Long
Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMillsecounds As Long)

'■キャラクター 構造体宣言
Type Chara
    X As Single
    Y As Single
    W As Single
    H As Single
    Lif As Long
    Typ As Long
    Par As Long
End Type

'●キャラクター 変数宣言
Public Player_Data As Chara


今回のゲームで使用する3つのAPIを宣言し、キャラクターを表す構造体を定義します。そして、定義した構造体を変数の型として、プレイヤーを管理する変数を宣言します。ここまでは大丈夫でしょうか。

ポイントは、プレイヤーを管理する『変数Player_Data』の扱いです。ここでは、モジュールの先頭でDimではなく、Publicを使って宣言しています。
こうすることで、この変数Player_Dataは、どのプロシージャからでも参照できるようになります。つまりスコープがVBA内部の全ての範囲に広がるわけですね。これは以前にも解説しました。

今回のゲームでは、プレイヤー処理以外の部分でも、この変数を参照する可能性があります。例えば、敵キャラクターが放った弾との当たり判定を行う場合など、絶対にプレイヤーキャラクターの情報が必要になりますよね。こんなときに、通常のプロシージャ内Dim宣言の変数は、外部のプロシージャから参照できないので使えません。
そのため、Publicを使って変数を宣言しておき、プロジェクト内のどのプロシージャからでも、この変数を参照できるようにしておくわけです。


次に、このモジュールの先頭部分で、『定数』というものも宣言しておきます。
定数は初めて登場した概念なので、簡単に解説しておきましょう。


■定数とは

定数は、あらかじめ内容の決まっている変数みたいなもの、と考えることができます。『定数』という名前からもわかるように、その中身の内容は常に一定していて、あとから変更することはできません
変数とは違って、あとから自由に中身を変えたりできないのです。これが何の役に立つのでしょうか? ちょっと疑問に思いますよね。

例えば皆さんが、唐突に『キャラクターを表すコントロールって幅いくつだっけ?』と聞かれて、即答できるでしょうか。よ~く読みながらここまで進めてきていれば、『18ポイントだよ!』とすんなり答えられるかもしれませんが、普通はなかなか即答できないと思います。いちいち覚えてられないですし、時間が経つとだんだん忘れていってしまうのが普通でしょう。
こんなとき、あらかじめ定数として『Player_Size』という定数を宣言しておけば、幅が何ポイントだったかなんて覚えておく必要はありません。

Const Player_Size As Long = 18

このように定数を宣言しておくと、コード内で、変数と同じようにPlayer_Sizeという定数を使うことができます。Long型で宣言されているので、整数データを扱うということが想像できると思います。

Constというキーワードに続けて、定数名、データ型を指定します。これは変数の宣言がDimからConstに変わっただけと思えば簡単ですね。
変数と異なる点は、最後にあらかじめ値を代入しておくという点です。先ほども書いたように、定数は後から内容を変更したりすることはできません。あくまでも、宣言の段階でデータを格納しておかなくてはいけません。

キチンと定数の宣言ができていれば、あとは変数と同じように、中身のデータを使ってコードを記述できます。例えば次のように記述した場合には、『9』というメッセージが出ます。

Const Player_Size As Long = 18

Sub Const_Test()
    Dim L As Long
    L = Player_Size / 2
    MsgBox L
End Sub

定数は、あくまでもプログラマーが開発を行いやすくするための仕組みです。絶対に使わなくてはならない、という類のものではありません。
しかし、私は定数をある程度積極的に使っていくことをオススメします。なぜなら、発展したゲームプログラミングを行うときには、定数が避けて通れない概念のひとつとなるからです。

例えば、一通りコードが書きあがってから、諸事情によりプレイヤーキャラクターの大きさを変更したくなったとしたらどうでしょうか。
色々な場所で、キャラクターの大きさを計算に使っていたとしたら、その全てを探し出して、ひとつひとつコードを修正しなくてはいけなくなります。
そんなとき、全てが定数で管理されていたとしたら、定数の中身を変えるだけで、全部の処理がスムーズに変更できます。あとからキャラクターを倍の大きさに変えたくなったとしたら、定数に代入している数値を『18』から『36』に変更するだけで、修正が済んでしまうわけです。

先々を見越して、修正しやすいコードを記述するためには、定数の概念が重要なのですね。ですから今回も、プレイヤーキャラクターの大きさは、定数で管理することにしてみましょう。
それと、キーの入力判定に使う定数も、併せて宣言しておきます。GetAsyncKeyState関数に渡すキーコードですね。
これもいちいち覚えていられないし、数字のままだとわかりづらいので、面倒だから定数化します。

それを踏まえた、現段階でのモジュール先頭部分が次のコードです。

'◆API 宣言
Declare Function GetTickCount Lib "kernel32" () As Long
Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMillsecounds As Long)

'■キャラクター 構造体宣言
Type Chara
    X As Single
    Y As Single
    W As Single
    H As Single
    Lif As Long
    Typ As Long
    Par As Long
End Type

'●キャラクター 変数宣言
Public Player_Data As Chara

'▲定数宣言
Public Const Left_Key As Long = 37 'カーソル左
Public Const Up_Key As Long = 38 'カーソル上
Public Const Right_Key As Long = 39 'カーソル右
Public Const Down_Key As Long = 40 'カーソル下
Public Const Esc_Key As Long = 27 'Escキー
Public Const Player_Size As Single = 9
'プレイヤーキャラクターを表す、イメージコントロールの実際の幅(大きさ)
'ただし、あとで使いやすいように2分の1にしておく
'実際は18ポイントなので9を設定、半分にする理由は次回以降

これで、現段階のモジュール先頭部分はOKです。


■忘れちゃいけない初期化処理

次にやらなくてはならないのが以前にも解説した『初期化処理』です。
ここであらかじめキャラクターの初期表示位置や、変数の状態などを設定しておきます。

ここはいきなりコードを載せてしまって解説します。

初期化処理を行うプロシージャ
Sub Init()
    With Player_Data
        .X = 100 '横位置を画面の中央
        .Y = 180 '縦位置を画面の下あたり
        .W = 0 'まだ使用しないので0
        .H = 0 'まだ使用しないので0
        .Lif = 1 '存在しているのでライフは0より大きい
        .Typ = 0 'プレイヤーはタイプ0
        .Par = 0 'まだ使用しないので0
    End With
End Sub

これが初期化処理を担当するプロシージャです。名前は『Init』としました。英語で『データなどを初期値にセットする・初期化する』という意味の『Initialize』から取っています。モジュールの先頭部分がキチンと準備できたら、その下にこのプロシージャを記述しておきましょう。

ここで実際にやっていることは、プレイヤーキャラクターを管理する変数Player_Dataの初期化だけです。これはあくまでも現段階での仕様です。
将来的には、ありとあらゆるゲーム内部のデータを、このプロシージャで初期化することになりますが、今はまだプレイヤーキャラクターだけなので、結構短いですね。

プレイヤーキャラクターは、横位置を表す『 X 』が初期値で100です。
ゲーム画面の役割を果たすフレームコントロールが、横幅200ポイント(Width = 200)に設定されていましたよね。ですから、ちょうどその半分となる100という数値は、フレームのちょうど中央になります。

そして同様に、縦の位置『 Y 』は180に設定しておき、画面の下のほうに表示されるようにしておきます。

232.gif

あとはほとんど現段階で使用しないので、とりあえず0(ゼロ)にしておきます。ライフだけは、死んでいるか生きているかを判断するのに使うので、とりあえず1にしておきます。
これで今回の初期化処理はOKです。

ただし、ここで注意して欲しいのは、今設定しているのは『あくまでもデータ上のキャラクターの位置』だということです。ここにデータを設定しただけでは、画面上には反映されません。
画面上に反映させるためには、別途、ユーザーフォーム上の処理を行う際の設定が必要です。と言われても、ちょっとわかりにくいですね。

感覚的には、絵を描こうと思ったが、頭に思い描いただけで実際に紙に描いたわけではない、という感じでしょうか。
この講座で解説する今回のシューティングゲームでは、先にデータを加工して、後からこのデータを使って画面を更新します。表示される位置の計算や、敵の撃った弾との当たり判定など、処理の大半は『Player_Data』などの変数で一気に行ってしまいます。これは今後登場する敵キャラクターなどの処理でも同様です。
変数などの内部的なデータで計算をして、全ての計算が完了した時点で、それらのデータをもとに、イメージコントロールなどを配置していくわけです。

画面上に実際に位置情報を反映させるのは、プレイヤーキャラクターを処理するプロシージャの中で行います。これは次回こそ、ほんとに解説できるはずです。

キャラクターの処理を行うプロシージャを作成し、キャラクターを正しく動作させるためには、今回の内容が絶対不可欠です。前置きが非常に長くなってしまって申し訳ないですが、焦らず、しっかり準備をしておいてください。


■格言

定数はあとから中身を変更できない
定数を使うと後から修正するのが容易
根本部分をしっかり設計


次回の内容を理解するためにも、今回の内容は重要です。






Chapter.45 [ シューティングゲーム5:プレイヤーキャラクター ]

■いよいよキャラクターの処理へ

さて、いよいよプレイヤーキャラクターの処理です。ここまでくるのが結構大変でしたね。
しかし、ゲームに限らず、プログラムは根本部分が最も大切です。ここまでの内容を順に習得してきたことで、皆さんのスキルは確実にアップしているはずですから、そこは自信を持っていいでしょう。

それではプレイヤーキャラクターの処理について、具体的に見ていくことにしましょう。

その名の通り、プレイヤーキャラクターは、ユーザーである第三者が自由に操作できるキャラクターでなくてはいけません。これを実現するためには、以前の講座で解説した『キーの入力判定』が必要です(Chapter36を参照)。ここまでは容易に想像がつきますね。
あとは、判定したキーの状態に応じて、適宜キャラクターを移動処理していけば、それだけで基本的なキャラクター処理部分は完成します。

この『キー入力判定+移動処理』を実装させて、プレイヤーが自由に操作するキャラクターを用意していくわけですが、今回大切なのはキー入力や移動処理以外の部分です。キー入力判定の細かい解説は以前の講座を参照していただくとして、今回はその他の部分で、特に重要な概念を中心に解説します。


■画面からはみださせない

ユーザーフォーム上のコントロールは、画面からはみだしたところに存在することもできます。つまり見た目上は見えない場所に、隠れて存在することができるということですね。これは移動処理のときや、初期化処理のときにも少し触れました。

ユーザーフォームの左上のカドが、横位置も縦位置も0(ゼロ)の場所。位置情報にマイナスの数値を指定すれば、いずれ見えなくなってしまうのでしたね。
ユーザーフォームが横幅100ポイントしかないのに、それ以上の数値を指定した場合にも同様のことが起こります。

この仕組みは、見せたくないものを隠したりするときには便利です。ですが、キャラクターを表示するときとなると、また話が違います。

今回のシューティングゲームでは、画面上からプレイヤーキャラクターが消えてしまっては困ります。かの有名なマリオブラザーズじゃあるまいし、右から出て行くと左から出てくるなんてわけにはいきません。(そういう設計にする場合は別ですが)

そこで、プレイヤーキャラクターの処理の中で、画面からはみださせないための仕組みを実装しておく必要があります。
キャラクターが今どこにいるか、そして、画面の大きさとキャラクターの大きさはどれくらいか、この合計3つの情報があれば、はみだし防止処理を行うことができるはずです。

キャラクターをはみださせないために
①キャラクターの位置はどこか?
②キャラクターの大きさはどれくらいか?
③ゲーム画面の大きさはどれくらいか?


■移動処理とはみだし防止処理

プレイヤーキャラクターを管理するプロシージャは、現段階では、次のようになります。いきなり少し長めのコードが出てきてしまいますが、後からゆっくりじっくり解説しますので、とりあえず見てください。

Sub Player_Action()

    With Player_Data

        'キー入力判定と移動処理 :①
        If GetAsyncKeyState(Left_Key) < 0 Then .X = .X - 3
        If GetAsyncKeyState(Up_Key) < 0 Then .Y = .Y - 3
        If GetAsyncKeyState(Right_Key) < 0 Then .X = .X + 3
        If GetAsyncKeyState(Down_Key) < 0 Then .Y = .Y + 3

        'はみだし防止処理 :②
        If .X - Player_Size < 0 Then .X = Player_Size
        If .X + Player_Size > 200 Then .X = 200 - Player_Size
        If .Y - Player_Size < 0 Then .Y = Player_Size
        If .Y + Player_Size > 200 Then .Y = 200 - Player_Size

    End With

    'フォーム上の『Player』を実際に移動する処理 :③
    With UserForm1.Player
        .Left = Player_Data.X - Player_Size
        .Top = Player_Data.Y - Player_Size
    End With

End Sub

これが、プレイヤーキャラクターの処理を担当する『Player_Actionプロシージャ』です。
現段階では、移動処理と、はみだし防止処理が施されている状態ですね。

①の部分が、『移動処理』を行っている部分です。
見てわかるとおり、GetAsyncKeyState関数を使って、4方向全てのキー入力を判定しています。もしもカーソルの左キーが押されていた場合には、横位置を表す『要素 X 』の値をマイナスします。横位置の定義は左に行くほどマイナス、右に行くほどプラスでしたね。左キーが押されているなら、要素 X はマイナスされるわけです。
もしも、右方向が押されているなら、先ほどとは反対方向なので、要素 X をプラスすればいいのですね。

同様に、縦位置の情報も処理します。縦位置の定義では、上に行くほどマイナス。下に行くほどプラスです。考え方は横位置のときと全く同じです。

②では『はみだし防止処理』をしています。
ゲーム画面の役割を果たしている、フレームコントロールの大きさは、縦横いずれも200ポイントだったはずです。ということは、プレイヤーキャラクターが、これよりも外の範囲に出て行ってしまわないように、逐一監視しなくてはいけません。

変数Player_Dataの、要素 X と、要素 Y は、キャラクターの中心から見た位置座標を表しています。
次の画像で言えば、黄色くマークされている部分です。

240.gif

しかし、ユーザーフォーム上での位置を表すプロパティ『Top・Left』は、中心ではなく『左上の座標』で位置情報を表します。

241.gif

このように、変数Player_Dataでの座標と、ユーザーフォーム上の座標では、座標位置の考え方が違っています。表示する位置を正しく計算するためには、ここで出ている座標上の差を考慮して、適切な処理を行わなくてはいけません。

ここで、前回解説した、『定数』が役に立ちます。

前回、初期化処理のコードの中で『定数Player_Size』を宣言しました。この定数Player_Sizeは、イメージコントロールの実際の幅をあらかじめ設定していましたね。覚えているでしょうか。

Public Const Player_Size As Single = 9

前回のコード内のコメントで『あとで使いやすいように2分の1にしておく。実際は18ポイントなので9を設定』と書いてありました。実は、2分の1にする理由は、この後の座標変換の考え方をよく見てみると、おのずとわかります。

242.gif

イメージコントロール自体の大きさは『18ポイント』でしたね。そして、定数Player_Sizeには、その半分の『 9 』をあらかじめ格納してあります。
なぜ2分の1の数値を格納したのかは、上の画像で考えてみるとわかると思います。

この仕組みが理解できていると、キャラクターの上下左右の辺が、どの座標になるか求めることができます。

キャラクターの上下左右の座標は……

左辺: X - 9
上辺: Y - 9
右辺: X + 9
下辺: Y + 9

で求めることができる

この考え方を元に、キャラクターが左の端に来ているときは、それ以上めり込めないように……上の端に来ているときは、それ以上めり込めないように……と、4つの方向全てをチェックします。これが、先ほど載せたコードの②の部分です。

もし、フレームの端よりも外側に出ていたとき(つまり各辺が0より小さいか、あるいは200より大きいとき)は、強制的に座標を端の部分に合わせてしまうわけです。これで、はみだし防止処理が実装できます。


さて、ちょっと長くてきついですが次です。次は③の部分ですね。
ここでは、実際のイメージコントロールの座標を設定しています。先ほどのはみだし防止処理の仕組みが理解できていれば、考え方は同じなので、なんとなくわかると思います。

イメージコントロールのLeft・Topが( 0 , 0 )だと仮定すると、その中心の座標となる X ・ Y は、( 9 , 9 )となります。もしLeft・Topが( 10 , 10 )だとすれば、 X ・ Y は、( 19 , 19 )ですね。

変数Player_Dataの要素 X と、要素 Y から、9を引き算した結果を求めて、それをコントロールのLeftと、Topに設定すればいいわけです。Withステートメントを使って、ユーザーフォーム上にあるPlayerという名前のイメージコントロールに対して、計算した結果を設定します。

この③の部分を設定した時点で、初めてユーザーフォーム上のイメージコントロールの位置が決定します。今まで、X や Y を使って計算してきた結果が、ここで初めて実を結ぶのです。

座標だのなんだのと、少し難しい考え方が出てきましたが、コードをよく見て、じっくり考えてみてください。どうしてもわかりづらい場合は、紙に四角形を書いて考えてみると、理解しやすいと思います。実際私は頭の中で、四角形をイメージしながらコードを書いていることが多いです。


補足コラム:If文の記述法

先ほどのコード内で、初めて特殊なIf文の記述が出てきました。
普通、If文は次のように記述します。

If 条件 Then
    条件が真の場合の処理~
End If

『If』から始まり『条件』そして『Then』と記述し、条件が真だった場合には中に記述された処理が行われます。それから最後は『End If』で終わるのがルールでしたね。
しかし、今回登場したIf文は次のように記述されています。

If 条件 Then 条件が真の場合の処理~

今までのIf文とは違い、全てのコードが1行で記述されている上、どこにも『End If』が見当たりませんね。
実は、これは文法上認められている『If文のもうひとつの書き方』なんですね。簡単な条件判断や、真の場合に行うべき処理が単純な場合などは、このような記述の仕方をすることで、コーディングにかかる手間を軽減できます。状況に応じて、使ってみるのもいいでしょう。ただし、私の試した感覚では、これによる処理の高速化などは特にないような気がします。わかりやすさを第一に据えて、自分が理解しやすいコードを書きましょう。



■ここまでを総まとめ

さて、座標系の計算は少し難しい処理でしたが、どうでしょう、ついてこれているでしょうか。ここで基本的な座標計算の考え方が理解できてしまえば、これから先、どのようなキャラクターなどでも同じ仕組みで処理していくことができます。
焦る必要はないので、しっかり習得してくださいね。

さて、ここまで出来たら最後の仕上げをしましょう。

あとやらなくてはいけないことは、『ゲームの終了処理』と、『コマンドボタンからのコードの呼び出し』の2つです。もう少しなのでがんばりましょう。

以前、メインプロセスを解説した講座で、『Main』という名前のプロシージャの雛形を載せました。今回はすでにゲームの作成に入っていますので、このMainというプロシージャも作成しましょう。それが次のコードです。

Sub Main() 'メインループとなるプロシージャ

    Dim Flg As Boolean
    Dim Stm As Long
    
    Call Init '①
    
    Flg = False
    
    Do Until Flg
        Stm = GetTickCount
        Call Player_Action '②
        DoEvents
        Do
            Call Sleep(1)
        Loop Until GetTickCount - Stm > 30
        If GetAsyncKeyState(Esc_Key) < 0 Then Flg = True '③
    Loop
    
End Sub

現段階では、まだプレイヤーキャラクターの処理しか作成していません(Player_Action)。ですのでメインループの中で呼び出しているサブプロシージャはひとつだけですね。

ここでのポイントは3つ。

①の部分では、まず初期化処理担当の『Init』を呼び出しています。これはメインループが始まる前に行っておかないと意味がありません。ループの中で呼び出してしまうと、ループするたびに毎回初期化処理が行われてしまいますので注意しましょう。

②でプレイヤーキャラクターの処理を呼び出しています。先ほど移動処理などを記述したあれですね。

③では、GetAsyncKeyState関数を使っていますね。前回の講座で宣言した定数Esc_Keyは、エスケープキーのキーコードを表しています。
つまり、もしエスケープキーが押されたときには、変数FlgTrueを代入して、ループが終了するようにしているのですね。
この仕組みがいわゆる『終了処理』です。ゲームのメインループから、キーの入力を受け取って抜ける仕組みですね。
これで、とりあえず現段階のメインプロセスは大丈夫です。


そして次に『コマンドボタンからのコードの呼び出し』です。これはじゃんけんゲームや、アニメーション処理などで、さんざん予行練習してきたので大丈夫でしょう。
ユーザーフォーム上にあるコマンドボタンの、クリックイベントから『Main』を呼び出せばいいのです。

Private Sub Com_Start_Click()
    Com_Start.Enabled = False '①
    Call Main '②
    Unload UserForm1 '③
End Sub

これがそのコードです。
まず①の部分で、コマンドボタンを『使用不可』の状態に設定しています。Enabledプロパティは、コントロールが使用できるかできないかを定義するプロパティです。ここに『False』を設定すると、そのコントロールは文字(Captionで設定した文字)が灰色に変わってしまい、使用できなくなります。
何度もクリックイベントが発生して、Mainが複数回呼び出されてしまわないようにするため、1回押された時点でコマンドボタン自体を使用できない状態にしてしまうわけです。

次に②。これは簡単ですね。CallでメインプロセスであるMainを呼び出しています。

③は、初めて目にするコードかもしれません。
Unload』というのはユーザーフォームを閉じなさい、という命令を出します。半角スペースを空けて、対象となるオブジェクトを指定すればいいので、今回は『UserForm1』としておきます。

え? フォームを閉じちゃっていいの? と思うかもしれませんが、これが実は大丈夫なんです。
②の部分で『Main』が呼び出されていますよね。プロシージャの中で別のプロシージャを呼び出した場合は、その処理が終わるまで、③の部分に処理が進むことはありません。
Mainの中で終了処理が行われて、完全にMainが終了した時点で③の部分にやっと処理が回ってきます。こうしておけば、先ほどのエスケープキーでの終了処理が行われると、そのままユーザーフォームが閉じるところまで、同時に行われるようになるのですね。

あとは、移動処理のところで行ったのと同様に、ユーザーフォームのクエリクロースイベントで、念のため全てのコードが止まるようにします。こうしておけば、ユーザーフォームが閉じられると同時に、確実に全てのプロシージャの処理が止まります。安全ですね。

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



■ここまでの全コード

かなり長い道のりでした。プライヤーキャラクターを作成したいだけなのに、なんでこんなにやることがてんこ盛りなんでしょうか……。しかし、これで基盤がほぼ完成したと言っていいでしょう。つまり、ここから先はプラモデルのパーツを継ぎ足すがごとく、プロシージャを追加していくだけで良いのです。

長く険しい道のりではありましたが、それだけ、価値のあることができたということでもあります。途中、何度か難しい部分があったはずです。
全てを一度に理解できなくても、ひとつひとつこなしていけば、必ずうまくいくはずです。焦ってはいけません。

とりあえず現段階のコードを全て以下に載せておきます。
全体像と、各プロシージャの役割や構造を、しっかり理解してください。必要に応じて、過去のChapterに戻って復習しながら、基盤をしっかり固めてください。

ここまでの全コード
'標準モジュール先頭部分

'◆API 宣言

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

'■キャラクター 構造体宣言
Type Chara
    X As Single
    Y As Single
    W As Single
    H As Single
    Lif As Long
    Typ As Long
    Par As Long
End Type

'●キャラクター 変数宣言
Public Player_Data As Chara

'▲定数宣言
Public Const Left_Key As Long = 37 'カーソル左
Public Const Up_Key As Long = 38 'カーソル上
Public Const Right_Key As Long = 39 'カーソル右
Public Const Down_Key As Long = 40 'カーソル下
Public Const Esc_Key As Long = 27 'Escキー
Public Const Player_Size As Single = 9
'プレイヤーキャラクターを表す、イメージコントロールの実際の幅(大きさ)
'ただし、あとで使いやすいように2分の1にしておく



'初期化を担当するプロシージャ

Sub Init()
    With Player_Data
        .X = 100 '横位置を画面の中央
        .Y = 180 '縦位置を画面の下あたり
        .W = 0 'まだ使用しないので0
        .H = 0 'まだ使用しないので0
        .Lif = 1 '存在しているのでライフは0より大きい
        .Typ = 0 'プレイヤーはタイプ0
        .Par = 0 'まだ使用しないので0
    End With
End Sub


'メインプロセスを担当するプロシージャ

Sub Main()

    Dim Flg As Boolean
    Dim Stm As Long
    
    Call Init
    
    Flg = False
    
    Do Until Flg
        Stm = GetTickCount
        Call Player_Action
        DoEvents
        Do
            Call Sleep(1)
        Loop Until GetTickCount - Stm > 30
        If GetAsyncKeyState(Esc_Key) < 0 Then Flg = True
    Loop
    
End Sub


'プレイヤーキャラクターを担当するプロシージャ

Sub Player_Action()

    With Player_Data

        'キー入力判定と移動処理 
        If GetAsyncKeyState(Left_Key) < 0 Then .X = .X - 3
        If GetAsyncKeyState(Up_Key) < 0 Then .Y = .Y - 3
        If GetAsyncKeyState(Right_Key) < 0 Then .X = .X + 3
        If GetAsyncKeyState(Down_Key) < 0 Then .Y = .Y + 3

        'はみだし防止処理 
        If .X - Player_Size < 0 Then .X = Player_Size
        If .X + Player_Size > 200 Then .X = 200 - Player_Size
        If .Y - Player_Size < 0 Then .Y = Player_Size
        If .Y + Player_Size > 200 Then .Y = 200 - Player_Size

    End With

    'フォーム上の『Player』を実際に移動する処理 
    With UserForm1.Player
        .Left = Player_Data.X - Player_Size
        .Top = Player_Data.Y - Player_Size
    End With

End Sub


'標準モジュールはここまで
'ここから下はフォームモジュールに記述


'コマンドボタンのクリックイベント

Private Sub Com_Start_Click()
    Com_Start.Enabled = False
    Call Main
    Unload UserForm1
End Sub


'ユーザーフォームのクエリクロースイベント

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


きっとブラウザ上でこのコードを見ると、非常に長く感じると思います。
ですが、VBEに貼り付け(もしくは自力で書く)した後に眺めると、実際はそうでもないです。多分。

プロシージャがそれぞれ切り離されているので、視覚的にはかなり見やすいはずです。『コードの部品化』ということが、どれほど効果的なのかということが、おのずとわかるのではないでしょうか。

ユーザーフォームのほうがしっかり出来ていれば、このコードを適宜貼り付けるだけで、プレイヤーキャラクターが画面上を自由に動くプログラムが完成します。
プログラムを終了したいときは、フォーム上の×ボタンをクリックするか、エスケープキー(Escキー)を押せばいいのですね。いずれの場合も、ユーザーフォームのクエリクロースイベントでコードが止まるので問題ありません。

自由気ままに、プレイヤーキャラクターを動かして遊んでみてください。

すると、勘のいい人なら気がつくはずです。ちょっとおかしい部分があることに……。
その謎は次回解説! 今回は長すぎましたね。お疲れ様でした。


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


■格言

座標計算を習得しよう
フォームの準備、各種宣言も忘れずに


焦らずひとつひとつこなす。これが今回もポイントになります。






Chapter.46 [ シューティングゲーム6:ショットを撃つ① ]

■前回の謎

さて、前回はかなり長い講座になってしまって、皆さん結構大変だったと思います。
ですが、前回の内容でも言いましたが、ほとんど全ての根本処理を実装させましたので、あとは継ぎ足し継ぎ足しで、どんどんゲームが完成に近づいていきます。ここからは少しペースアップできるはずですから、がんばって進めていきましょう。

まずは、前回の最後に書いた『勘のいい人なら気がつく、おかしなところ』というなぞなぞの答えから考えます。

前回の処理を実装させてコードを実行すると、プレイヤーキャラクターが自由に画面上を動き回ります。このとき目いっぱい右下にキャラクターを寄せてみると、何がおかしいのかきっとわかります。

250.gif

何がおかしいのかわかりましたか?
では、左上に目いっぱい寄せた場合と比較してみましょう。

251.gif

右下に寄せた場合だけ、キャラクターが画面の外側にめり込んでしまっていますね。座標計算は間違っていないのに、どうしてこのような現象が起こるのでしょうか。実はこれ、フレームのせいなんです。

フレームコントロールはその名の通り、ユーザーフォーム上の空間を切り取るフレームの役目を果たします。枠線が表示されて、そこだけが独立した空間になるのですね。
しかしこの枠線、思いっきり拡大してみると、こんな感じになってます。

252.gif

ご覧の通り、この枠線は2ピクセル分の幅があります。つまり、両端それぞれ2ピクセルの合計4ピクセルが、枠線として使われているのです。
これをポイント単位に変換すると、『 4 × 0.75 = 3 』という式が成り立ちますね。ですからフレームコントロールのWidthプロパティが『200ポイント』だとすると、そのうち3ポイントは枠線なので、黒くなっている中身の空間は『197ポイント』しかないということになるわけです。

253.gif

これでは左右対称な処理ができなくなってしまいますので、ちょっと困ります。座標計算は画面の大きさを200として計算しています。この仕組みがキチンと動作するために、フレームコントロールの大きさを、適切なかたちに修正しておきましょう。

具体的には、現状200ポイントに設定されているWidthプロパティを、枠線の分だけ広くすればいいのです。ですからフレームコントロールのWidthHeightを『203ポイント』に修正しておきましょう。
こうしておけばプロシージャ内のコードを修正しなくとも、キチンと端の部分でキャラクターが止まるようになります。はみだし防止処理が完全になりました。謎が解けてすっきりです。


■ショットを撃てるようにするためには

今回の主題『ショットを撃つ』について考えてみましょう。
プレイヤーキャラクターがショットを撃つということは、移動処理と同様、プレイヤーのキー入力をチェックして処理しなくてはいけません。ここまではなんとなく想像がつきますね。

ですので、まずはモジュールの宣言部分、『定数宣言』に次のコードを追加します。

Public Const Shot_Key As Long = 90 'Zキー

これはキーボードのZキーを表すキーコードを、定数として宣言している部分です。ここで定数としてキーコードを宣言しておけば、あとで処理がしやすいですね。
そしてもうひとつ、ショットの大きさを定義する定数です。

Public Const Shot_Size As Single = 3

プレイヤーキャラクターのときにも、その実際の大きさを定数化しましたね。あれと同じ意味のものです。

次に、Publicを使ってひとつ変数を宣言します。

Public Shot_Interval As Long 'ショットの間隔

この『変数Shot_Interval』は、プレイヤーの放つショットの間隔を調節するのに使います。どういうことか、具体的に説明しましょう。


ここまで進めてきた皆さんは、既にメインループについて十分な知識を身につけていると仮定して、これから先の内容を説明していきます。よくわからない人は以前のChapterをよく読んで、理解してから挑戦してみてください。

メインループでは、同期処理を行って、1秒間に何回ループするか定義していましたね。
前回載せたメインループのコードをよく見ていただければわかりますが、今回のシューティングゲームは1秒間に30回強、ループするようになっています。
でもこれ実はものすごい速さです。1回あたり約0.03秒ですからね。
まぁこれも、人間からみるとものすごい速さですが、昨今のパソコンにとっては大したスピードでもないのだからすごいですよね。

とにかく、普通は、人間がキーを入力するとき、0.03秒でキーを押して離してが出来るとは思えません。タイピングのプロじゃあるまいし、そんなにものすごく速いスピードでシューティングゲームを遊ぶのは無理です。
しかしループ中は、毎回キーの入力をチェックしていますよね。
もし、キーが押されていることを判断材料としてショットを発射してしまうと、1秒間に30回以上ショットが発射されてしまいます。これはちょっと多すぎです。処理能力的にも見ても、これは少し無理があります。

そこで、先ほど宣言した変数Shot_Intervalを有効に使って、この問題を解決します。

ショットが発射された瞬間から、それ以降のループの中で、この変数を毎回プラス1ずつカウントするようにします。そして、一定の数までカウントされるまでは、ショットを再び発射することができないようにしてしまいます。
例えば、変数Shot_Intervalが10になるまでカウントするとすれば、1秒間に約3発のショットが発射できることになります。1秒間に約30回ループするからですね。
カウント待ちする回数を増やせば間隔が長くなり、少なくすれば、間隔が短くなります。今回はカウント待ち回数を『 7 』に設定してやってみます。これなら、1秒間に約4~5発程度のショットが発射されるようになります。

If Shot_Interval = 0 Then
    If GetAsyncKeyState(Shot_Key) < 0 Then
        ショットを発射する
        Shot_Interval = 1
    End If
Else
    Shot_Interval = Shot_Interval + 1
    If Shot_Interval > 7 Then Shot_Interval = 0
End If

さて、上のコードを見てください。
これは、前回記述したプロシージャ『Player_Action』に追加する、ショットを管理するためのコードです。
先ほどPublicで宣言した変数Shot_Intervalをどのように使っているかがポイントとなります。まず、始めから条件分岐による判断を行っていますね。変数Shot_Intervalの値が0かどうかを判断しています。

変数Shot_Intervalの値がもし0だった場合には、これはショットを撃てる状態であると判断します。ですのでキーが押されているかどうかその次のIf文でGetAsyncKeyStateを使って判断しています。もしキーが押されていた場合は、その後のショットを発射する部分に処理が移ります。

ひとたびショットを発射したら、変数Shot_Intervalの値を1にします。ショットが発射されると同時に、この変数Shot_Intervalの値が1になることで、この次のループが回ってきたときには一番最初のIf文を通ることが出来なくなります。よって、キーが押しっぱなしになっていてもショットは絶対に発射されません。
変数Shot_Intervalが0じゃないとき、この場合はショットが発射できないと判断するわけです。
しかしこのままでは二度とショットが撃てなくなります。ですから、変数Shot_Intervalが0ではなくなった場合の処理が必要ですね。それが『Else』以降の部分ですね。

ここでは変数Shot_Intervalを毎回プラス1ずつ増やしていきます。そして、その値が7より大きい、つまり8になった瞬間に、再び変数Shot_Intervalを0に戻します。

こうすることで、次のループでは、もしキーが押されていればショットが発射できるようになります。

このように、変数Shot_Intervalを有効に利用して、理論上は8ループに1回しかショットが発射されないようにするわけですね。

今回載せたコードは『ショットを発射する』の部分がまだ未完成です。これは次回作成するとして、その時に完成版コードを載せるようにするつもりです。とりあえず今回はその考え方や仕組みを理解できていればいいでしょう。


■ショットの扱い

さて、これでショットを発射するための『プレイヤーキャラクター側の準備』はできました。あとやらなくてはならないのは、『ショットそのものを準備する』ということです。

具体的には、次のようなことをやらなくてはいけません。

ショットの画像を準備する
ショットが生成されるプロシージャを作成する
ショットの移動を管理するプロシージャを作成する

今回は、画像をイメージコントロールにロードするところまでやってしまいましょう。

ショットの画像は非常に小さいです。ちょっとわかりづらいかな?

Shot01.gif

この画像を使って、ショットに見立てたイメージコントロールを用意します。
ショット用の画像は大きさが8×8ピクセルです。ということは……もうわかりますね。ショット用のイメージコントロールのWidthプロパティとHeightプロパティは6ポイントに設定すればいいわけです。

今回はプレイヤーキャラクターが放つショットとして、最大で画面上に5発まで発射できるようにしたいと思います。そこで、ユーザーフォーム上に、5つのイメージコントロールを追加します。

254.gif

言うに及ばず、これらの追加したイメージコントロールも、枠線の消去や、透過処理設定をしておきます。これは以前の講座で詳しく解説しましたね(Chapter24Chapter25を参照)。

イメージコントロールの名前の変更はキチンとやっておきましょう。次回以降のコードでは、この名前でイメージコントロールを指定します。特別な理由がない限りはこの名前をしっかり設定しておいてくださいね。

そして、忘れてはならないのが、このショット用イメージコントロールのVisibleプロパティの設定です。
Visibleプロパティは、そのコントロールが見えているかいないかを定義するプロパティです。もし、このまま画面上に見えてしまっていたら、撃たれていないショットが画面上に置いてあるみたいで、なんだか変ですよね。

そこで、これらの5つのイメージコントロールは、全てVisibleプロパティをFalseに設定しておきます。こうしておけば、ユーザーフォームが実際に表示されるときには、画面上から見えなくなります。画面上からなくなってしまうわけではありません。あくまでも、見えないだけです。
なくなってしまったわけではないので、あとでコードの中でVisibleプロパティにTrueを設定すれば、キチンと画面上に現れます。それはショットを発射するときに行いますので、現段階ではVisibleプロパティにFalseが設定されていれば大丈夫です。

これで、ユーザーフォーム上のコントロールは準備できました。

次回は、ショットを撃つためのコードを記述します。今回の講座内容をキチンと理解しておいてください。


■格言

フレームは枠線を考慮した大きさに設定
ループ内でカウントして適切なショット間隔に


プレイヤーキャラクターの弾数はとりあえず5発でやります。






Chapter.47 [ シューティングゲーム7:ショットを撃つ② ]

■ショットはキャラクター

さて、前回の続きです。今回の内容をしっかり習得できれば、プレイヤーキャラクターがショットを撃てるようになります。これでかなりゲームらしくなります。楽しみですね。

ショットを撃つの2回目となる今回は、ショットの『生成と移動』について考えてみます。

プレイヤーキャラクターを作成する際には、ユーザーのキー入力を受け取って、キャラクターが移動するようにしましたね。ゲームが行われている最中、プレイヤーのキー入力によって移動する方向が決まるわけですから、どのような移動を行うかは常に一定していないわけです。

これに対してショットの場合は、一度発射されたらそのあとプレイヤーのキー操作などに影響される必要はありませんね。単純に決められた動きを延々と続ければいいだけです。
ですから、考え方としては『動きが一定の規則によって定義されたキャラクター』と同じです。プレイヤーキャラクターを移動処理したときと同じように、ショットを一種のキャラクターとして考えれば、別段難しいこともありませんね。

ショットは上で書いたように、一種のキャラクターとして考えます。
そこで、プレイヤーキャラクターを作成したときと同じように、まずはショット専用の構造体を用意しましょう。

Type Shot
    X As Single 'ショットの横位置
    Y As Single 'ショットの縦位置
    Vis As Boolean 'ショットが見えているかどうか
    Typ As Long 'ショットのタイプ
End Type

この構造体は、敵キャラクターの撃つショットにも使えるようにしています。まぁ、細かいことは使いながら説明していきます。

次に、この構造体を使って、ショット用の配列変数を用意します。

Public P_Shot_Data(4) As Shot

変数の型が『~As Shot』となっていますね。先ほど定義した構造体を使っていますので、間違えないように注意しましょう。
前回も書いたように、今回はショットを5発撃てるようにしますので、配列のインデックスの指定が『P_Shot_Data(4)』という感じに4を指定しています。0から始まり4までの合計5つ分です。

これらをモジュールの先頭部分に追加すれば、下準備はOKです。


■追加したものは初期化する

さて、上記のように、何か扱うものが増えると(今回で言えばショット)、変数や構造体、定数などが新しく増えていきますね。
ここで忘れがちなのが、『追加した変数などの初期化処理』です。私はこれ結構やってしまうんです。
そこで、今回のショット関連で増えた変数などは、初期化処理でしっかりリセットするようにしておきましょう。プレイヤーキャラクターの構造体を初期化している『Init』という名前のプロシージャがありましたね。
あのプロシージャの中に、今回増えた配列や変数を初期化する処理を追加します。

今回追加するべきなのは、『P_Shot_Data()』と、前回登場したPublic宣言の変数『Shot_Interval』の2つです。

Sub Init()
    With Player_Data
        .X = 100
        .Y = 180
        .W = 0
        .H = 0
        .Lif = 1
        .Typ = 0
        .Par = 0
    End With
    Erase P_Shot_Data
    Shot_Interval = 0

End Sub

緑の文字で表示されている部分が、今回新しく追加された部分です。
まず、緑文字の1行目、『Erase P_Shot_Data』と記述されています。
ここで登場する『Erase』は、全ての配列の要素を初期化する命令を出します。数値を扱う変数型なら初期値は『 0 』。文字列を扱う変数型なら初期値は『 "" (長さ0の文字列)』。TrueFalseを扱うブール型なら初期値は『 False 』です。
これらの法則にしたがって、配列の全ての要素が初期化されます。P_Shot_Dataは配列変数です。ですからEraseを使って初期化することが可能です。

Eraseは配列しか初期化できません。ですので、配列ではなく通常の変数である『Shot_Interval』には直接0を代入しておきます。

これで初期化はOKです。Eraseの使い方がポイントですね。


■ショットの生成

ショットに関しては、『生成』と『移動』を分けて管理するようにします。これはショットの誕生から消滅までを見てみるとわかりやすいと思います。

プレイヤーキャラクター処理の中でキー入力を受け取り、今ショットを撃とうとしていると判断された場合
    ▼
ショット生成プロシージャを呼び出す
    ▼
ショットが生成される
    ▼
ショットが生成されたので、以降、移動を管理するプロシージャが毎回ループごとにショットを処理する
    ▼
ショット移動プロシージャが、ショットが画面上から消えていないかなどを判断し、もし消えていたり敵に当たっていたりした場合にはショットを消す

上の簡易フローを見るとわかりますが、プレイヤーキャラクター処理の中で呼び出されるのは、『ショット生成プロシージャ』です。そして、一度ショットが生成されたら、あとは『ショット移動プロシージャ』がショットの状態を管理します。

前回載せた、プレイヤーキャラクターのショットに関する処理の中で、『ショットを発射する』の部分だけが保留になっていましたね。これに相当するのが『ショット生成プロシージャの呼び出し』です。

Sub P_Shot_Begin(X As Single, Y As Single, Typ As Long)
    Dim L As Long
    For L = 0 To 4
        With P_Shot_Data(L)
            If Not .Vis Then 'ショットが見えていないなら :①
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True '見えるようにする :②
                UserForm1.Controls("P_Shot" & L + 1).Visible = True
                '実際に見えるようにする :③
                Exit For
            End If
        End With
    Next
End Sub

これが『ショット生成プロシージャ』になります。その仕組みを詳しく見てみましょう。

まず、始めに理解しておくべきは、このプロシージャが3つの引数を取る、ということです。一番最初、プロシージャの名前に続けて『X As Single, Y As Single, Typ As Long』という記述がありますね。この部分が引数を定義している部分です。これを見ると、このP_Shot_Beginというプロシージャが『X・Y・Typ』という3つの引数を取ることがわかります。

引数を取るプロシージャということは、このプロシージャを呼び出すときに値をセットにして呼び出さなくてはいけません。この引数の仕組みは、じゃんけんゲームを作成したときにも解説しましたね(Chapter20を参照)。
これが理解できていないと、多分この後の解説についてこれません。よくわからない人はよく復習しておきましょう。

さて、続いてはプロシージャの中身です。
このプロシージャでは、For文を使っています。先ほど構造体を使って宣言した、ショット用の配列変数がありましたね。『P_Shot_Data()』がそれですね。
この構造体の要素の中に『Vis:ショットが見えているかどうか』という要素がありましたね。まずはFor文を使って、画面上に見えていないショットを探します。それが①の部分です。
Shot構造体の要素であるVisは、Boolean型のデータに定義されていました。つまり、TrueFalseかどちらかのデータが入っているはずですね。もし、『Vis = True』ならショットは見えている。『Vis = False』なら、ショットは見えていない、となるわけです。

For文を使って0から4までを順番に見ていき、見えていないショットを探します。『If Not .Vis Then』の部分がそれを判断しているところですね。

補足コラム:Notの使い方

『Not』はIf文などの条件判断を行う処理において、真と偽の評価を反転させます。例えば、If文の場合には、次のように考えることが出来ます。

    If Bool Then …… 変数BoolがTrueなら
    If Not Bool Then ……変数BoolがFalseなら
    If L = 0 Then …… 変数Lが0なら
    If Not L = 0 Then …… 変数Lが0以外なら

これは条件判断を行うところなら基本的に使えます。たとえば『Do Until』など、繰り返し処理の終了条件にも使えます。

    Do Until Flg …… 変数FlgがTrueなら終了
    Do Until Not Flg …… 変数FlgがFalseなら終了

他にも、Boolaen型変数の値を反転させるなどの利用法もあります。

    Bool = Not Bool …… 変数BoolがTrueならFalseに、FalseならTrueに

最初はとっつきにくいかもしれませんが、有効に利用すればかなり役に立ちますので、覚えておいて損はないでしょう。

さて、①の部分で、見えていないショットが見つかったらショットを生成します。
ショットの生成では、構造体の各要素に、順番に値をセットしていきます。
.X = X 』という式は一見するとわかりにくいですが、『左辺のピリオドつき .X 』は構造体の要素 X です。それに対し『右辺のピリオド無し X 』は、引数から受け取ったデータ X です。
プロシージャの呼び出しと同時に引数として受け取ったデータを、構造体の要素に代入しているのですね。

同様に、要素 Y や要素 Typ を順に設定して、要素VisTrueにします。これでデータ上は、ショットが見えていると判断される状態になりました。ここまでが②の部分です。

そして、③のところでは、実際にユーザーフォーム上のイメージコントロールを見えるようにしています。
UserForm1.Controls("P_Shot" & L + 1).Visible = True』がその処理を行っている部分です。

ユーザーフォームに配置した、ショット用のイメージコントロールは、その名前が『P_Shot1P_Shot5』までの連番になっていましたが覚えているでしょうか。
ここでは、『UserForm1.Controls(コントロールの名前)』という指定の仕方を使って、イメージコントロールのVisibleプロパティをTrueにしています。こうすることで、画面上のイメージコントロールが実際に見えるようになります。

そして最後、1つショットを撃てれば充分なので、それ以降の繰り返し処理を行わないように『Exit For』しておきます。これをやっておかないと、見えていないショットが全て発射されてしまうので、おかしなことになります。

ここまで出来れば、ショットの生成についてはOKです。
あとは、プレイヤーキャラクターの処理の中で、ショットを撃つべきだと判断された場合にこのプロシージャを呼び出すようにすればいいわけです。これは最後にまとめて全体のコードを載せますので、そこで詳しく解説しますね。


■ショットの移動

さて、ちょっと長いですが、がんばってショット関連の処理を作ってしまいましょう。
次は、ショットの移動処理についてです。

Sub P_Shot_Action()

Dim L As Long

For L = 0 To 4
    With P_Shot_Data(L)
        If .Vis Then '①
            .Y = .Y - 6 '②
            If .Y < -5 Then .Vis = False '③
            With UserForm1.Controls("P_Shot" & L + 1)
                If P_Shot_Data(L).Vis Then
                    .Left = P_Shot_Data(L).X - Shot_Size
                    .Top = P_Shot_Data(L).Y - Shot_Size
                Else
                    .Visible = False '④
                End If
            End With
        End If
    End With
Next
    
End Sub

①から順番に見ていきましょう。
まずは、ショット自体が今現在見えているのかどうか、If文を使って判断します。それが①ですね。

そして、ここで見えていると判断された場合には、②で示すように、データ上での座標を上方向に移動しています。縦位置を表す要素 Y をマイナスすることで、座標が上の方向にずれるわけです。

座標移動ができたら、次に画面上から消えてしまっていないか判断しています。それが③の部分。フレームの一番上の端が0に相当するわけですから、縦位置がマイナスの数値になってしまっていたらショットは完全に画面上から消えていることになります。
そんなときは、見えているかどうかを定義している要素VisFalseを代入しておき、見えていないことにしておくのです。

その後、実際のユーザーフォーム上のコントロールを、今計算した座標上に移動させます。これはプレイヤーキャラクターのときと同じですね。
構造体で定義した変数のデータで全ての計算を行ってしまい、それらの結果を使ってコントロールの位置やVisibleプロパティを設定します。これが④の部分ですね。

やっていることはプレイヤーキャラクターのときとほとんど同じです。

構造体で定義した要素『 X・Y 』は、ショットの中心の座標を表します。ですから、前回の講座で定義してある『定数Shot_Size』を使って、適切な『Top・Left』に変換しているわけです。


■ここまでの全コード

それでは、前回と今回で解説した、ショットに関するコードを全て取り入れてみましょう。
プレイヤーキャラクターの時に載せたコードより、追加された部分は緑の文字で表示するようにしていますので、適宜追加してみてください。

ここまでの全コード
'標準モジュール先頭部分

'◆API 宣言

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

'■キャラクター 構造体宣言
Type Chara
    X As Single
    Y As Single
    W As Single
    H As Single
    Lif As Long
    Typ As Long
    Par As Long
End Type

'■ショット 構造体宣言
Type Shot
    X As Single
    Y As Single
    Vis As Boolean
    Typ As Long
End Type


'●キャラクター 変数宣言
Public Player_Data As Chara

'●ショット 変数宣言
Public P_Shot_Data(4) As Shot

'◎その他 変数宣言
Public Shot_Interval As Long

'▲定数宣言
Public Const Left_Key As Long = 37 'カーソル左
Public Const Up_Key As Long = 38 'カーソル上
Public Const Right_Key As Long = 39 'カーソル右
Public Const Down_Key As Long = 40 'カーソル下
Public Const Esc_Key As Long = 27 'Escキー
Public Const Shot_Key As Long = 90 'Zキー
Public Const Player_Size As Single = 9
'プレイヤーキャラクターの実際の大きさ
Public Const Shot_Size As Single = 3
'弾各種の実際の大きさ



'初期化を担当するプロシージャ

Sub Init()
    With Player_Data
        .X = 100
        .Y = 180
        .W = 0
        .H = 0
        .Lif = 1
        .Typ = 0
        .Par = 0
    End With
    Erase P_Shot_Data
    Shot_Interval = 0

End Sub

'メインプロセスを担当するプロシージャ

Sub Main()

    Dim Flg As Boolean
    Dim Stm As Long
    
    Call Init
    
    Flg = False
    
    Do Until Flg
        Stm = GetTickCount
        Call Player_Action
        Call P_Shot_Action
        DoEvents
        Do
            Call Sleep(1)
        Loop Until GetTickCount - Stm > 30
        If GetAsyncKeyState(Esc_Key) < 0 Then Flg = True
    Loop
    
End Sub


'プレイヤーキャラクターを担当するプロシージャ

Sub Player_Action()

    With Player_Data

        'キー入力判定と移動処理 
        If GetAsyncKeyState(Left_Key) < 0 Then .X = .X - 3
        If GetAsyncKeyState(Up_Key) < 0 Then .Y = .Y - 3
        If GetAsyncKeyState(Right_Key) < 0 Then .X = .X + 3
        If GetAsyncKeyState(Down_Key) < 0 Then .Y = .Y + 3

        'はみだし防止処理 
        If .X - Player_Size < 0 Then .X = Player_Size
        If .X + Player_Size > 200 Then .X = 200 - Player_Size
        If .Y - Player_Size < 0 Then .Y = Player_Size
        If .Y + Player_Size > 200 Then .Y = 200 - Player_Size

        'ショット生成
        If Shot_Interval = 0 Then
            If GetAsyncKeyState(Shot_Key) < 0 Then
                Call P_Shot_Begin(.X, .Y, 0)
                Shot_Interval = 1
            End If
        Else
            Shot_Interval = Shot_Interval + 1
            If Shot_Interval > 7 Then Shot_Interval = 0
        End If


    End With

    'フォーム上の『Player』を実際に移動する処理 
    With UserForm1.Player
        .Left = Player_Data.X - Player_Size
        .Top = Player_Data.Y - Player_Size
    End With

End Sub


'ショット生成プロシージャ

Sub P_Shot_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                UserForm1.Controls("P_Shot" & L + 1).Visible = True
                Exit For
            End If
        End With
    Next

End Sub



'ショット移動管理プロシージャ

Sub P_Shot_Action()

    Dim L As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If .Vis Then
                .Y = .Y - 6
                If .Y < -5 Then .Vis = False
                With UserForm1.Controls("P_Shot" & L + 1)
                    If P_Shot_Data(L).Vis Then
                        .Left = P_Shot_Data(L).X - Shot_Size
                        .Top = P_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next

End Sub



'標準モジュールはここまで
'ここから下はフォームモジュールに記述


'コマンドボタンのクリックイベント

Private Sub Com_Start_Click()
    Com_Start.Enabled = False
    Call Main
    Unload UserForm1
End Sub


'ユーザーフォームのクエリクロースイベント

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


う~ん……長い。

緑の文字で表示されている部分が、今回追加されたコードです。そこに要点を絞って考えると、だいたいわかるのではないでしょうか。

特に難しいと思われるのが、水色の文字で表示されている部分、ショット生成プロシージャの呼び出しです。
ショットの生成を担当する『P_Shot_Beginプロシージャ』は、引数を3つ取ります。この引数で与えられた座標に、生成されるショットの初期位置をセットするのです。

ですから、プレイヤーキャラクターの現在の座標を引数として渡して、その場所にショットが生成されるようにするのですね。
プレイヤーキャラクターが、『 X = 100 , Y = 100 』の座標にいるとすれば、引数として(100,100,0)がP_Shot_Beginプロシージャに渡されます。
そうするとP_Shot_Beginプロシージャが、その座標に新しくショットを生成するというわけです。
ちなみに、今回は引数として渡す『Typ』は、特に使用しないので0に設定しています。

どのプロシージャから、どのプロシージャが呼び出されているのか、そこをしっかり理解していれば、おのずとわかるはずです。焦らず、時系列を正しく把握してみてくださいね。

かなり長かったですが、これでプレイヤーキャラクターがショットを発射できるようになったはずです。ユーザーフォームを実行してみてください。キーボードのZキーを押すと、プレイヤーキャラクターがショットを発射するようになったはずです。
うまくいかないという人は、ゆっくりでもいいですから、ひとつひとつエラーをつぶしていきましょう。エラーが起きないのにうまくいかない場合は、どこかしらで設定を間違えているはずです。最初からひとつひとつこなしていけば、きっとうまくいくはずですからがんばってみてください。


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


■格言

初期化を忘れずに
ショットの生成と移動は分けて考える


だいぶ複雑になってきましたね。焦りは禁物です。






Chapter.48 [ シューティングゲーム8:Mod演算子の活用 ]

■敵キャラクター下準備

前回までの講座内容で、プレイヤーキャラクターがショットを撃つところまでは作成できました。随分ゲームらしくなってきましたね。

今回からは敵キャラクターについて考えていきたいと思います。
とは言っても、敵キャラクターは、基本的にキー入力などの受け付けをする必要がありません。出現するべきポイントを決めてあげさえすれば、あとは勝手に移動や攻撃をするようにしておくだけです。やり方としてはショットのときの考え方に近いかもしれません。

プレイヤーキャラクターの処理を行う際に、キャラクター用の構造体をすでに宣言しています。敵キャラクターもこれを流用して作成することが可能です。
今回は、画面上に最大で10体まで表示できるようにしてみましょう。

それでは、まず敵キャラクターを管理する配列変数の宣言です。先ほども書いたように、これにはプレイヤーキャラクターに使用した構造体を流用します。

Public Enemy_Data(9) As Chara

この1文で、敵キャラクター10体分の配列変数を宣言できました。データ型を間違えないように注意してくださいね。

次に、ユーザーフォームの準備です。
こちらは、専用の画像を用意しました。

     敵その1          敵その2     
Blue.gif
Yellow.gif


今回は2種類の敵キャラクターが表示されるようにしてみたいと思います。

先ほども書いたように、今回は敵キャラクターを10体まで表示できるようにします。そこで、ユーザーフォーム上に、10個の新しいイメージコントロールを配置しましょう。
今回は、敵用のイメージコントロールに『Enemy1Enemy10』までの連番で名前をつけておきます。ここで名前を連番にしておくことで、ショットの処理のときと同様、繰り返し処理との相性が非常によくなります。

260.gif

ただし、今回はこれで準備完了とはいきません。なぜなら、敵キャラクターを2種類用意するからです。
そのためには、アニメーションのときに解説したテクニックを併用します。具体的に言うと『原画を準備しておく』ということです。

もちろん、敵キャラクターの1番から5番までを敵その1、6番から10番までを敵その2、という具合に切り分けて処理することもできなくはありません。ですが、敵キャラクターの出現と同時に好きなように切り替えられる、というようにしたほうが、あとあと何かと便利ですし、コードもスマートに仕上がります。

以前、アニメーション処理を行った際には、あらかじめユーザーフォーム上に原画となるイメージコントロールを3つ配置していました(あの丸いキャラクターのときです)。そして、イメージコントロールから別のイメージコントロールへ、画像データの転送を行っていましたね。
あれと同じ要領で、今回は敵キャラクターの種類に応じて、表示される画像が変化するようにするわけです。

そこで、原画用のイメージコントロールを、ユーザーフォームの大きさを変更して配置します。デザインしている段階では、ユーザーフォームが大きくなっていても問題ありません。ユーザーフォームが表示される直前、つまり『Initializeイベント』で適切な大きさに調節すればいいからです。これについては以前にも詳しく解説しましたね。よくわからないという人はそちらをご覧ください(Chapter40を参照)。

261.gif

このように、デザイン中はユーザーフォームの大きさを、余裕のある大きさにしておきます。そして、最終的には隠してしまう部分に、原画となるイメージコントロールを配置しておきます。
この2つのイメージコントロールの名前は『Enemy_s1 ・ Enemy_s2』としておきます。
敵キャラクターを実際に表示する段階になったら、その種類に応じて、適切な敵キャラクターの画像をここから転送してあげます。このようにしておくことで、何番目のイメージコントロールを使うときでも、画像は自由に切り替えて処理することができるのです。


Initializeイベントを修正

さて、先ほどの敵キャラクター表示切り替えのために、ユーザーフォームを大きくしてしまいました。これはゲームの開始と同時に調節して、原画用のイメージコントロールが見えなくなるようにしなくてはいけません。

Private Sub UserForm_Initialize()
    UserForm1.Width = 220
End Sub

フォームモジュールに、このようなInitializeイベントを追加しておきます。これで、ユーザーフォームが表示される直前に、ユーザーフォームの横幅が適切な状態に調節されます。原画用のイメージコントロールがうまい具合に隠れるはずです。


それからもうひとつ、あらかじめ設定しておくべきことがあります。
ショットの処理を行ったときもそうでしたが、敵キャラクターは、ゲームが始まった瞬間から画面上に表示されていてはおかしいですね。先ほどの画像みたいに、画面上にズラリとならんでいるのは変です。
そこで、敵キャラクター用のイメージコントロールは、あらかじめVisibleプロパティを『False』に設定しておきます。こうしておけば、ゲームのスタート時には、敵キャラクターが視覚的には見えなくなります。

敵キャラクターが出現する段階になったら、『種類に応じて画像を設定』→『出現させたい座標にセット』→『VisibleプロパティをTrueに設定』→『その後の移動などの処理へ』と順番に処理していくわけです。

ここまでが実装できれば、ユーザーフォームの準備はOKです。
あとは、敵キャラクターの出現するタイミングをいかに管理するかですね。


■敵キャラクター出現のタイミング

さて、それでは実際に敵キャラクターを出現させるために、必要となる概念について考えて見ましょう。

世の中には、様々なゲームがあり、その種類に応じて、様々な敵キャラクターが存在します。
例えばスーパーマリオブラザーズに登場する『クリボー』や『ノコノコ』。ドラゴンクエストに登場する『スライム』や『キメラ』。その他にも、ギャラクシアンに登場する様々な虫型の敵達や、ツインビーに登場する『ポコポコ大魔王』などなど、ゲームによって千差万別です。

そして、その出現方法もまた、千差万別ですね。
マリオのような横スクロール系のアクションゲームでは、キャラクターを移動して画面がスクロールすると、向こうのほうから敵が出現します。ドラゴンクエストはランダムにエンカウントして敵が出てきますね。

今回作成するのは『縦スクロールのシューティングゲーム』でしたよね。ということはツインビーのような感じで敵が出現するようにするわけですね。具体的には、自動的に画面がスクロールしていき、決まった場所に到達すると敵キャラクターが出現するという感じです。
このような敵キャラクターの出現をプログラミングするには、一体どうしたらいいのでしょうか。

これを実現するための方法として、私の感覚で一番簡単だと思うのは、『内部カウンター方式』です。
この『内部カウンター方式』というのは一般的な手法ではなくて、あくまで私が名づけた手法のひとつです。そこらへんは誤解しないでくださいね。多分検索とかしても何も出ません……きっと。

さて、では内部カウンター方式とは具体的にはどのような方法なのか、それを解説しましょう。

まず、この手法を実現するために、Publicでひとつ変数を宣言します。

Public Total_Count As Long

この変数、カタカナ読みすると『トータルカウント』と読めますね。この変数をうまく使うことで内部カウンター方式が実現できます。

ゲームが始まったら、最初にこの変数Total_Countを0にしておきます。これは初期化処理のところで、他の変数同様に初期化しておけばいいですね。
そして、ゲームの実行中、ループごとに毎回プラス1ずつ増やしていきます。すると、ゲームが進めば進むほど、この変数Total_Countの値が増加していくことになりますね。ここまでくればなんとなく想像がつきませんか?

この変数Total_Countの値が小さいうちは、ゲームが開始されてからまだそれほど時間が経っていないことになります。逆に、大きな値が入っているとしたら、長い時間経っていることになりますね。

ゲームの開始と同時にカウントを始め、一定の数値に来たときにだけ、敵キャラクターが出現するようにすれば、任意のタイミングで敵キャラクターを出現させることができるわけです。
間隔を空けずに、短いカウントの中で連続して出現させることもできますし、逆に少し間を空けることも簡単です。

今回は、この敵キャラクター出現を管理するプロシージャを、がんばって作成してみることにしましょう。


■敵キャラクター出現管理

今回作成するのは、あくまでもテスト段階用のプロシージャです。

普通、シューティングゲームは敵の出現するパターンが決まっています。今作成しているシューティングゲームも、最終的には一定のパターンで敵が出現するようにします。
ただ、テストの段階では、同じパターンで敵が何度も出現してくれたほうが、動作を確認しやすいです。そんな理由から、今回の講座で解説するコードは、あくまでもテスト用とします。

それではコードを見ながら考えてみましょう。

Sub Stage_Count()
    Select Case Total_Count Mod 200 '①
        Case 0
            敵キャラクター出現処理A
        Case 50
            敵キャラクター出現処理B
        Case 100
            敵キャラクター出現処理C
    End Select
End Sub

これが、敵キャラクターの出現を管理するプロシージャです。
このプロシージャの名前は『Stage_Count』としました。厳密には複数のステージがあるわけではないのですが、便宜上、このような名前にさせていただきます。

さて、このプロシージャで肝となるのは、ズバリ①の部分です。というか、ここがほとんど全てです。

まず、Select Case文を使っているのは見てわかりますね。そして、条件分岐の判断材料となる部分に『Total_Count Mod 200』と書いてあります。
この式を見て、皆さんは意味がわかるでしょうか。

Mod』というところで、多分クエスチョンマークが浮かぶと思います。ですのでまずはこれについて解説しますね。

Mod』は、演算子と呼ばれているもののひとつです。
演算子は『+-÷×』などの、計算に使う記号と思ってもらえればわかりやすいと思います。数字と数字を、何かしらの法則によって計算するための記号ですね。

Mod演算子は、考え方としては『÷』に近いです。皆さん小学生の頃に『余りのある割り算』を勉強したと思いますが、要するにあれです。
いくつか例を挙げてみましょう。

10 Mod 10 …… 10 ÷ 10 = 1......余り0
10 Mod 5 …… 10 ÷ 5 = 2......余り0
10 Mod 4 …… 10 ÷ 4 = 2......余り2
10 Mod 3 …… 10 ÷ 3 = 3......余り1

この計算は大丈夫ですね? 割り切れない数で除算した場合には、小数点以下まで計算しなければ必ず余りが出ます。Mod演算子はこれを一発で計算してくれるという便利な演算子なのです。

これが理解できたところで、もういちど先ほどのコードを見てみます。

Select Case Total_Count Mod 200

変数Total_Countは、ゲームの開始と同時に常にカウントされ続けます。1から始まり、2、3、4……11、12、13……101、102、103……と永遠に増え続けます。この数字を200で割り算してみると余りはどのようになるでしょうか。

Total_Countの値Mod 200 を計算した結果
10
10
50
50
100
100
200
0
250
50
350
150
500
100

このように、Mod演算子を使うと、『ある特定の範囲に限定した数値』を取得することができます。
例えばTotal_Countが199の時には、『 199 ÷ 200 』を計算すればいいので、答えは『 199 ÷ 200 = 0......余り199 』になりますね。次のループではTotal_Countの値が200になるはずですから『 200 ÷ 200 = 1......余り0 』という計算が成り立ち、Mod演算子の計算結果は0に戻ります。

つまり、ゲームが進行している最中、『Total_Count Mod 200』の計算結果は、常に0~199の範囲をぐるぐる回り続けるのです。
この値をSelect Case文の条件分岐にかけることによって、任意のタイミングで処理を行うことが可能になります。Mod演算子の計算結果が0のときは……50のときは……100のときは……という具合ですね。

変数Total_Countの値がどれほど大きくなっても、常にMod演算子による計算結果は一定の範囲に限定されます。これがポイントです。
Mod演算子をうまく活用することによって、繰り返し同じ範囲の数値を取得することができるのですね。

これは、テスト用の敵出現管理として理想的な処理と言えるでしょう。何度も同じ敵が繰り返し出現するので、動作がおかしくないかチェックしやすいです。


最終的にゲームが完成に向かう段階に入ったら、Select Case文で直接カウント数を参照して、敵の出現を管理してしまうのもありです。それが例えば次のコードです。

Sub Stage_Count()
    Select Case Total_Count '①
        Case 0, 200, 400, 800
            敵キャラクター出現処理A
        Case 50, 500, 1000
            敵キャラクター出現処理B
        Case 100, 600, 650, 700, 900
            敵キャラクター出現処理C
    End Select
End Sub

今度は①の部分でMod演算子を使っていませんね。変数Total_Countの値を直接使ってSelect Case文で分岐しています。

この方法では、先ほどとは違って同じパターンで敵が何度も出現することはありません。なぜなら、ループごとにカウントされる変数Total_Countは、いずれ1000を超えていってしまうからですね。

カウント数が1000以下の場合には、指定のカウント数で敵が出現しますが、そのあとは一切何も出現しません。Select Case文で指定されている最大数が1000なので、それ以上の数値になってしまったら、絶対に敵の出現処理に移る事はないわけです。

どのようなタイミングで敵が出現するようにするのかは、ゲームの大筋が完成してから考えればいいでしょう。ですから、今の段階では、動作のチェックがしやすいテスト仕様のコードを使っていきましょう。


■まとめ

さて、少し長くなりました。
途中、Mod演算子に関することなど、少し横道にそれたので、わかりにくくなってしまったかもしれませんね。
ただ、Mod演算子は使い方さえ理解できていれば、ゲームの開発にはとても役立ちます。今回の講座で覚えてしまえばきっと損はしません。

とりあえず今回は、ユーザーフォーム上の準備をしっかりやっておきましょう。さらに、敵の出現するタイミングをどのように管理したらいいのか、それがわかっていれば充分です。

実際の敵出現処理は次回解説です。次回の内容を実装させたら、敵キャラクターが出現するようになりますので、より一層ゲームらしくなるはずです。がんばりましょう。


■格言

敵キャラクターをしっかり準備する
カウント数を参照して敵の出現を管理する
Mod演算子をうまく活用


Modは超使えます。是非習得してほしいですね。






Chapter.49 [ シューティングゲーム9:敵キャラクター登場 ]

■敵キャラクター登場のプロセス

前回は敵キャラクターの出現方法を管理するやり方について解説しました。
特殊な計算を簡単に行うことができるMod演算子についても、簡単に説明しましたね。

一見するとちょっとわかりづらい内容だったかもしれません。Mod演算子は使いこなせればかなり有用な演算子だと思います。特に、複雑な動作を要求されるゲームの設計においては、欠かすことのできない要素のひとつだと思います。
今すぐに絶対必要というものでもないのですが、覚えておいて損はないですので、皆さんがんばって習得してほしいと思います。

さて、前回の内容を踏まえて、今回は実際に敵キャラクターが登場するようにしてみましょう。
敵キャラクターの出現には、ショットを実装したときと同じような考え方で挑みます。具体的に言うと、『敵の初期配置』と『敵の移動などの処理』を分けて作成します。

前回作成した『敵の出現するタイミングを管理するプロシージャ』が、あらかじめ決められたタイミングで、敵キャラクターが登場する場面であるかどうかを判断します。
そして、敵が登場するべきタイミングだと判断された場合には、『敵を画面上に配置するプロシージャ』を呼び出すようにします。
これで『敵の初期配置』が完了した状態になるわけですね。

次回以降のループでは、『敵の移動を管理するプロシージャ』が、敵キャラクターを移動させたり、ショットとの当たり判定などを行うわけです。
このプロシージャは、敵キャラクターが一切登場していないときでも呼び出されます。ループが1回まわるごとに、必ず呼び出すようにしておくわけです。ただし、画面上に敵が1体も登場していないときには、何もしません。呼び出されても、何にも処理を行わずに終わってしまいます。敵が登場している間は、適宜、敵キャラクターの移動などを行うように設計するのですね。

ちょっとわかりにくいかもしれませんが、実際にコードを書きながら、見ていくことにしましょう。


■敵の初期配置

まずは、敵キャラクターの初期配置です。このプロシージャが、前回解説した『敵出現のタイミングを管理するプロシージャ』から呼び出されます。

実際にコードを見ながら考えていきます。

Sub Enemy_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif = 0 Then '①
                .X = X
                .Y = Y
                .W = Enemy_Size '②
                .H = Enemy_Size '②
                .Typ = Typ
                .Lif = 1
                .Par = 0
                With UserForm1.Controls("Enemy" & L + 1) '③
                    .Visible = True
                    Select Case Typ '④
                        Case 1
                            .Picture = UserForm1.Enemy_s1.Picture
                        Case 2, 3
                            .Picture = UserForm1.Enemy_s2.Picture
                    End Select
                End With
                Exit For '⑤
            End If
        End With
    Next
    
End Sub

さて、どうでしょう。少し長いですが、順番に説明していきますので、がんばってください。

このプロシージャは、『Enemy_Begin』という名前にしました。そして、プロシージャ名に続く括弧の中に、3つの引数が定義されていますね。
3つの引数はそれぞれ、『 X が横位置』、『 Y が縦位置』、『 Typ がタイプ』を表しています。呼び出されたときに、これらの引数からデータを受け取って、その情報を元に敵キャラクターを配置するわけです。

前回定義した敵キャラクター用の変数を覚えていますか? 『Enemy_Data(9)』という配列変数でしたね。この配列の要素の数は、出現する敵キャラクターの数です。要素数に9が指定されているということは、全部で10体分のデータをこの配列で管理できます。

プロシージャが開始されると、まずはこの配列全てを参照するためにFor文での繰り返し処理が始まります。そして、①の部分で、キャラクターが現在表示されているかどうかを判断しています。
敵キャラクターの生存を管理する『Lif要素』が0ということは、そのキャラクターはまだ画面上に表示されていません。敵キャラクターを新しく配置するわけですから、このライフが0のデータが見つかった場合だけ次に進みます。

続いて、敵キャラクターのデータに、引数から受け取った位置情報やタイプを設定していきます。ここで②を見てください。プレイヤーキャラクターのときと同様、『定数』を使って敵キャラクターの幅を設定しています。この定数はまだ宣言をしていませんでしたので、最後にまとめて載せるコードに、宣言を追加しておきます。忘れずにあとでチェックしておいてくださいね。

Enemy_Data()にデータを全て代入できたら、続いてユーザーフォーム上のイメージコントロールの設定をします。これが③ですね。
ショットのときと同様、『UserForm1.Controls(コントロールの名前)』という指定の仕方を使って、イメージコントロールに設定を施していきます。

今回ポイントとなるのが、④の部分です。
敵キャラクターは2種類の画像から、タイプによって適切な画像を選択するようにします。そのために、Select_Case文で、引数Typの値を調べて処理を分岐させます。
タイプが1のときは、青いタイプの敵キャラクターの画像を、タイプが2と3のときは黄色いタイプの敵キャラクターの画像を使うようにしています。画像が2種類なのに、敵キャラクターのタイプが3種類になっていますね。この理由は最後まで読んでいただければわかります。

そして、全ての設定が完了したら、⑤で示すように、繰り返し処理を抜けています。こうしておかないと、非表示の敵キャラクターが全て表示されてしまうので注意しましょう。

これで、『敵の初期配置プロシージャ』はOKです。構造としてはショットの配置プロシージャと非常に似ています。引数を受け取り、それらの情報を元にキャラクターを配置しているだけですからね。ひとつひとつ見ていけば、それほど難しくないはずです。焦らずよく見て考えてくださいね。


■敵キャラクター移動管理

さて、続いては敵キャラクターの移動を管理するプロシージャを作成していきましょう。
このプロシージャは、敵が出現しているかどうかに関わらず、毎回ループの中で呼び出されます。
もし敵キャラクターが表示されていた場合には、処理を行いますが、それ以外のときは何もしません。そのあたりを念頭に置いて見ていきましょう。

Sub Enemy_Action()

    Dim L As Long
    Dim TCMOD
    
    TCMOD = Total_Count Mod 30 '①
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif > 0 Then '②
                Select Case .Typ
                    Case 1
                        .Y = .Y + 1.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 1
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 1
                End Select
                If .X > 220 Then .Lif = 0 '③
                If .Y > 220 Then .Lif = 0 '③
                If .X < -20 Then .Lif = 0 '③
                If .Y < -20 Then .Lif = 0 '③
                If TCMOD = 0 Then '④
                    Select Case .Typ
                        Case 2
                            .Typ = 3
                        Case 3
                            .Typ = 2
                    End Select
                End If
            End If
        End With
        With UserForm1.Controls("Enemy" & L + 1) '⑤
            If Enemy_Data(L).Lif > 0 Then
                .Left = Enemy_Data(L).X - Enemy_Size
                .Top = Enemy_Data(L).Y - Enemy_Size
            Else
                .Visible = False
            End If
        End With
    Next
    
End Sub

少し長いですが、最初から順番に見ていきましょう。

このプロシージャでは冒頭に『TCMOD』というLong型の変数を宣言しています。この変数は、『Total_CountMod計算した結果』を扱うために使います。
これは何のためかといいますと、敵キャラクターの挙動を管理するためです。前回、Mod演算子を有効に活用することで、敵キャラクターを同様のパターンで繰り返し出現するようにしましたね。あれと同様の仕組みを使って、敵キャラクターの挙動を管理するために、この変数を使います。

①の部分で、この変数TCMODに、『Total_Count Mod 30』の計算結果を代入します。Mod演算子で、除算した余りを代入するのですね。ですから、変数TCMODの値は、常に『0~29』の範囲に収まり続けます。
30回ループするたびに、変数の値が0に戻るわけですね。

変数TCMODの値が0になるのは、30ループに1回です。それを判断材料として処理すれば、30ループごとに処理を切り替えることができるのです。これは後で出てきますので、とりあえず仕組みだけ覚えておきましょう。
 
続いては②です。敵キャラクターが画面上に存在しているということは、要素Lifが1以上に設定されているはずです。ここでそれを判断しています。
もし要素Lifが0だった場合には、敵キャラクターは存在していないわけですから、何も処理を行うことなく素通りするようにしています。

敵キャラクターが存在していた場合には、敵キャラクターのタイプに応じて位置を移動します。
タイプ1の場合には、まっすぐ下方向へ移動するだけです。
タイプ2と3は、斜めに移動しながら下の方向へ動きます。タイプ2は右斜め下方向へ、タイプ3は左斜め下方向へむかって移動します。

③の部分は、敵キャラクターの現在位置をチェックして、画面上から完全に消えてしまっていないかどうかを判断しています。上下左右全ての方向をチェックして、画面からはみだして消えてしまった敵キャラクターを、存在していないことにしているのですね。

そして④の部分で、先ほど登場した変数TCMODが出てきます。
ここでは、先ほど説明したように、30ループごとに処理が変化するようにしています。実は、敵キャラクター用の画像が2種類しかないのに、敵キャラクターのタイプが3種類あったのは、ここでその理由がハッキリします。

先ほども説明したように、タイプ2の敵キャラクターとタイプ3の敵キャラクターは斜めに移動します。30ループごとにタイプを2と3で交換するようにすれば、左右にジグザグ走行する敵キャラクターを表現できます。
あくまでもタイプ1の敵キャラクターは『直進型』。タイプ2と3は、30ループごとに切り替えながら処理することで『ジグザグ走行型』となるわけです。
敵キャラクターの出現時には、タイプ2とタイプ3を同じ画像に設定しましたね。これも、ここでの処理を実現するためだったのです。

さてここまでできたら、あとは⑤で示すように、ユーザーフォーム上のコントロールを実際に動かします。
上で散々計算してきた結果を、ここでイメージコントロールに設定するのです。
これで、画面上に表示されている敵キャラクターが実際に移動します。


■その他のやっておくべきことは

さて、これで敵キャラクターを扱うための、プロシージャの準備はできました。あとやるべきことはなんでしょうか。

結構忘れてしまいがちですが、『定数の宣言』や『初期化』は忘れずにやっておきましょう。今回は『Enemy_Size』という新しい定数が増えています。これは敵キャラクターの実際の大きさを表す定数です。これをモジュールの先頭で宣言しておきます。

そして、敵キャラクターのデータを扱うための配列変数『Enemy_Data』の初期化処理も忘れずに。これは配列変数なので、ただ単に初期化するだけなら、ショットのときと同様『Erase』が使えますね。

その他にも、『変数Total_Countの初期化』と、ユーザーフォームの『Initializeイベント』の追加があります。やることが少し多いですが、全体像を把握しながら、適宜追加しておきましょう。

焦りは禁物です。下に全コードを載せます。それを参考に、地道にコードや設定を追加していきましょう。


■ここまでの全コード

さて、それではここまでの全コードを掲載しておきます。色が変化している部分は、ショットの処理を行った時点から、あらたに追加されている部分です。

'標準モジュール先頭部分

'◆API 宣言

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

'■キャラクター 構造体宣言
Type Chara
    X As Single
    Y As Single
    W As Single
    H As Single
    Lif As Long
    Typ As Long
    Par As Long
End Type

'■ショット 構造体宣言
Type Shot
    X As Single
    Y As Single
    Vis As Boolean
    Typ As Long
End Type

'●キャラクター 変数宣言
Public Player_Data As Chara
Public Enemy_Data(9) As Chara

'●ショット 変数宣言
Public P_Shot_Data(4) As Shot

'◎その他 変数宣言
Public Shot_Interval As Long
Public Total_Count As Long 'ゲーム中のカウンタ

'▲定数宣言
Public Const Left_Key As Long = 37 'カーソル左
Public Const Up_Key As Long = 38 'カーソル上
Public Const Right_Key As Long = 39 'カーソル右
Public Const Down_Key As Long = 40 'カーソル下
Public Const Esc_Key As Long = 27 'Escキー
Public Const Shot_Key As Long = 90 'Zキー
Public Const Player_Size As Single = 9
'プレイヤーキャラクターの実際の大きさ
Public Const Shot_Size As Single = 3
'弾各種の実際の大きさ
Public Const Enemy_Size As Single = 9
'敵キャラクターの実際の大きさ



'初期化を担当するプロシージャ

Sub Init()
    With Player_Data
        .X = 100
        .Y = 180
        .W = 0
        .H = 0
        .Lif = 1
        .Typ = 0
        .Par = 0
    End With
    Erase P_Shot_Data
    Erase Enemy_Data
    Shot_Interval = 0
    Total_Count = 0
End Sub

'メインプロセスを担当するプロシージャ

Sub Main()

    Dim Flg As Boolean
    Dim Stm As Long
    
    Call Init
    
    Flg = False
    
    Do Until Flg
        Stm = GetTickCount
        Total_Count = Total_Count + 1
        Call Stage_Count
        Call Player_Action
        Call P_Shot_Action
        Call Enemy_Action
        DoEvents
        Do
            Call Sleep(1)
        Loop Until GetTickCount - Stm > 30
        If GetAsyncKeyState(Esc_Key) < 0 Then Flg = True
    Loop
    
End Sub


'敵キャラクターの出現管理プロシージャ

Sub Stage_Count()
    Randomize
    Select Case Total_Count Mod 200
        Case 0
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 1)
        Case 50
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 2)
        Case 100
            Call Enemy_Begin(Int(Rnd * 120) + 40, -10, 3)
    End Select
End Sub



'プレイヤーキャラクターを担当するプロシージャ

Sub Player_Action()

    With Player_Data

        'キー入力判定と移動処理 
        If GetAsyncKeyState(Left_Key) < 0 Then .X = .X - 3
        If GetAsyncKeyState(Up_Key) < 0 Then .Y = .Y - 3
        If GetAsyncKeyState(Right_Key) < 0 Then .X = .X + 3
        If GetAsyncKeyState(Down_Key) < 0 Then .Y = .Y + 3

        'はみだし防止処理 
        If .X - Player_Size < 0 Then .X = Player_Size
        If .X + Player_Size > 200 Then .X = 200 - Player_Size
        If .Y - Player_Size < 0 Then .Y = Player_Size
        If .Y + Player_Size > 200 Then .Y = 200 - Player_Size

        'ショット生成
        If Shot_Interval = 0 Then
            If GetAsyncKeyState(Shot_Key) < 0 Then
                Call P_Shot_Begin(.X, .Y, 0)
                Shot_Interval = 1
            End If
        Else
            Shot_Interval = Shot_Interval + 1
            If Shot_Interval > 7 Then Shot_Interval = 0
        End If

    End With

    'フォーム上の『Player』を実際に移動する処理 
    With UserForm1.Player
        .Left = Player_Data.X - Player_Size
        .Top = Player_Data.Y - Player_Size
    End With

End Sub


'ショット生成プロシージャ

Sub P_Shot_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If Not .Vis Then
                .X = X
                .Y = Y
                .Typ = Typ
                .Vis = True
                UserForm1.Controls("P_Shot" & L + 1).Visible = True
                Exit For
            End If
        End With
    Next

End Sub


'ショット移動管理プロシージャ

Sub P_Shot_Action()

    Dim L As Long

    For L = 0 To 4
        With P_Shot_Data(L)
            If .Vis Then
                .Y = .Y - 6
                If .Y < -5 Then .Vis = False
                With UserForm1.Controls("P_Shot" & L + 1)
                    If P_Shot_Data(L).Vis Then
                        .Left = P_Shot_Data(L).X - Shot_Size
                        .Top = P_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next

End Sub


'敵キャラクター生成プロシージャ

Sub Enemy_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif = 0 Then
                .X = X
                .Y = Y
                .W = Enemy_Size
                .H = Enemy_Size
                .Typ = Typ
                .Lif = 1
                .Par = 0
                With UserForm1.Controls("Enemy" & L + 1)
                    .Visible = True
                    Select Case Typ
                        Case 1
                            .Picture = UserForm1.Enemy_s1.Picture
                        Case 2, 3
                            .Picture = UserForm1.Enemy_s2.Picture
                    End Select
                End With
                Exit For
            End If
        End With
    Next
    
End Sub



'敵キャラクターを担当するプロシージャ

Sub Enemy_Action()

    Dim L As Long
    Dim TCMOD
    
    TCMOD = Total_Count Mod 30
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif > 0 Then
                Select Case .Typ
                    Case 1
                        .Y = .Y + 1.5
                    Case 2
                        .X = .X + 0.5
                        .Y = .Y + 1
                    Case 3
                        .X = .X - 0.5
                        .Y = .Y + 1
                End Select
                If .X > 220 Then .Lif = 0
                If .Y > 220 Then .Lif = 0
                If .X < -20 Then .Lif = 0
                If .Y < -20 Then .Lif = 0
                If TCMOD = 0 Then
                    Select Case .Typ
                        Case 2
                            .Typ = 3
                        Case 3
                            .Typ = 2
                    End Select
                End If
            End If
        End With
        With UserForm1.Controls("Enemy" & L + 1)
            If Enemy_Data(L).Lif > 0 Then
                .Left = Enemy_Data(L).X - Enemy_Size
                .Top = Enemy_Data(L).Y - Enemy_Size
            Else
                .Visible = False
            End If
        End With
    Next
    
End Sub




'標準モジュールはここまで
'ここから下はフォームモジュールに記述


'コマンドボタンのクリックイベント

Private Sub Com_Start_Click()
    Com_Start.Enabled = False
    Call Main
    Unload UserForm1
End Sub


'ユーザーフォームのクエリクロースイベント

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


'ユーザーフォームのイニシャライズイベント
Private Sub UserForm_Initialize()
    UserForm1.Width = 220
End Sub


コード全体だと非常に長いですが、やっていることは限られた範囲のことだけです。見た目に臆せず、じっくりやってくださいね。

ここまでが完成すると、敵キャラクターが順番に画面上に登場するようになります。

タイプ1の敵は、『青い見た目、直進型』です。タイプ2と3の複合タイプは『黄色い見た目、ジグザグ型』です。Mod演算子をうまく使って、敵キャラクターの出現タイミングを管理するようにしていましたよね。これにより、一定の順番で繰り返し敵キャラクターが出現します。

あえて詳しくは解説しませんが、敵キャラクターは乱数を用いて表示位置を決定するようにしています。
敵キャラクターの初期配置を担当する『プロシージャEnemy_Begin』は、3つの引数を取ります。順番に、『横位置』、『縦位置』、『敵のタイプ』の3つですね。今回は横位置の部分を乱数でランダムに変更しているので、毎回ランダムな場所から敵キャラクターが出現する仕組みです。
引数の順番に注意しながら、コードをよ~く見れば、おのずとわかると思います。

以前の講座の内容までで、プレイヤーキャラクターの移動処理や、放ったショットの移動処理は完成しているので、こちらも普通に動かせます。
今回までの内容では敵キャラクターに『あたり判定』がついていませんので、ショットが当たっても何も起こりませんが、とりあえず、完全にシューティングゲームっぽい感じになったと思います。

実際にキチンと動いたときの感動は、多分、今までで一番大きいんじゃないかなと思いますので、ぜひ試してみてください。

今回は多分、今までで一番文章量が多くなってしまいました……。
少しわかりにくいと思いますので、追加した部分を一覧にしておきます。皆さん、がんばってくださいね。

■変数宣言
    Enemy_Data(9) を宣言......敵キャラクターデータ用
    Total_Count を宣言......ゲーム中のカウンタ用

■定数宣言
    Enemy_Size を宣言し9を代入......敵キャラクターの実際の大きさ

■初期化処理プロシージャ『 Init 』に追加
    Enemy_Data をEraseで初期化
    Total_Count に0を代入し初期化

■メインプロセス『 Main 』に追加
    Total_Count を毎回プラスする処理
    プロシージャ『 Stage_Count 』の呼び出し
    プロシージャ『 Enemy_Action 』の呼び出し

■プロシージャの追加
    Stage_Count
    Enemy_Begin
    Enemy_Action
    ユーザーフォームの Initialize イベント



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


■格言

変数・定数の宣言を忘れずに
初期化処理を忘れずに
タイプごとに異なる挙動を実現する


目新しい技術はありません。要は応用力と持続力です。






Chapter.50 [ シューティングゲーム10:衝突判定 ]

■衝突判定

いよいよ本講座も50回を迎えました。これもひとえに、皆さんの励ましの声があってのことだと思っています。気長に足を運んでくださっている方々に、感謝します。

さて、記念すべき50回目の講座は『衝突判定』について考えてみます。
前回の講座で、敵キャラクターが登場するようになりました。これにより、かなりゲームらしくなりましたね。しかし、敵キャラクターが登場するようになっただけで、あたり判定等は未実装です。ここを実装させると、シューティングゲームとしての最低限の要素は揃います。気合を入れてがんばりましょう。

衝突判定を考えるとき、最も大切なのはその概念の理解です。どのようにして衝突判定を行えばいいのか。あるいは、どのような計算を行うことによって、結果的に衝突を検知することができるのか。ようはその考え方をしっかりと知っておく必要があるのです。

衝突判定の正しい概念が理解できていないと、非常に無駄なコードを書くことになったり、そもそも正しく衝突を検知できないなんてことにもなりかねません。まずは考え方をしっかり理解してください。


■概念を理解する

実際の世界では、衝突しているかしていないかを考える場合に、大抵は、1対1で考えます。『ボールとバット』とか、『車と電柱』とか、判断の対象となるのが1つの物体です。
衝突しているかどうかは目で見たり、耳で聞いたりして判断しますね。普通は目で見て当たっているかどうかわかりますし、目が見えない状態でも、音がすれば当たっているということはわかります。

しかしプログラミングの世界では、こんな簡単にはいきません。全てが数字で管理されるコンピューターの世界では、目で見て判断するというわけにはいきません。もちろん、ぶつかって勝手に音が鳴るなんてことはありませんしね。

それでは一体どのようにして衝突を判定すればいいのでしょうか。
衝突判定を行うためには、最低でも次のような情報が必要です。

対象となるオブジェクトの位置
対象となるオブジェクトの大きさ

この2つです。

簡単な例を挙げて見てみましょう。

270.gif

上の画像で、2つの物体は衝突していますか? 衝突していませんよね。
では次の画像はどうでしょうか?

271.gif

これは明らかに衝突していますよね。
つまり、衝突判定とは、2つのオブジェクトに重なっている部分があるかどうかを判定することなのです。
物体Aと物体Bに、座標上で重なりがある場合は、これは衝突していると言えます。逆に、どこも重なっていない場合は、これは衝突しているとは言えません。

物体Aの位置と大きさ、そして、物体Bの位置と大きさ、これらを元に計算を行い座標上の重なりを判定します。


■4つの方向で判断する

目で見て重なりがあるかどうか判断するのは簡単ですが、プログラム上でこれを行うとなると、結構面倒です。

以前、プレイヤーキャラクターが画面からはみださないようにするために、座標計算を行ったのを覚えているでしょうか(Chapter45を参照)。
はみだし防止処理の際には、上下左右4つの方向全てで、画面からはみだしていないか判定していました。これと同じように、衝突判定を行うためにも上下左右全ての方向で重なりを判定します。

物体Aの横位置が X1 、縦位置が Y1 。横幅が W1 、縦幅が H1 です。
物体Bの横位置が X2 、縦位置が Y2 。横幅が W2 、縦幅が H2 です。
それを踏まえて次の画像で考えてみましょう。

272.gif

テーブルの上に置いてある箱をイメージしてください。
この2つの物体がぶつかるためには、横位置をお互いに近づけていけばいいですよね。つまりお互いの距離を小さくしていけば、いつかは衝突します。

273.gif

まだ衝突してません……。

274.gif

はい、これで衝突しましたね。
これでわかるように、横位置から、衝突しているのかを判定するには、『横位置』と『横幅』をうまく組み合わせればいいのです。
上の画像で言えば、次のように計算できますね。

物体Aの X1 + (W1 / 2) が、物体Bの X2 - (W2 / 2)よりも大きい

それでは次に、2つの物体の位置を逆転して考えてみましょう。

275.gif




276.gif

この場合は、次のような式が成り立ちますね。

物体Aの X1 - (W1 / 2) が、物体Bの X2 + (W2 / 2)よりも小さい


どうですか? 画像と組み合わせて考えれば結構簡単ですね。
そして、これは左右だけでなく、上下の関係についても同様に考えることができます。

277.gif

物体Aの Y1 + (H1 / 2) が、物体Bの Y2 - (H2 / 2)よりも大きい
物体Aの Y1 - (H1 / 2) が、物体Bの Y2 + (H2 / 2)よりも小さい


これらの4つの条件の全てに適合しているということは、すなわち物体Aと物体Bには、共有する座標領域があるということになります。つまり衝突していると判断することができるわけです。

これを踏まえて、実際のコードに直してみましょう。
ゆっくり考えれば大丈夫です。落ち着いて、4つの座標全てを、頭の中でイメージしながら確認してみてください。

2つの物体の衝突判定

X1 + (W1 / 2) > X2 - (W2 / 2)
X1 - (W1 / 2) < X2 + (W2 / 2)
Y1 + (H1 / 2) > Y2 - (H2 / 2)
Y1 - (H1 / 2) < Y2 + (H2 / 2)

この全ての条件式にあてはまるとき、2つの物体は衝突している。



■衝突判定のまとめ

2つのオブジェクトの衝突を判定するための方法、理解できたでしょうか。
衝突判定の方法は、実は非常に多彩です。しかしそれらの多くが、複雑な計算式や、数学的知識を必要とします。難しいものになると、私もよくわからないです。ほんとに。

今回解説した衝突判定の方法は、実に単純で簡単です。しかし、実際にはこれが理解できていればほとんどの場合は大丈夫です。実際私も、ほとんどこれしか使ってませんし……。2次元での衝突判定は、これで完璧、心配いりません。

ポイントとなるのは4つの方向全てで、座標の計算を行うという点です。
全ての条件に当てはまっている場合は衝突が起こっています。一見すると面倒でわかりにくいかもしれませんが、落ち着いて、ひとつひとつイメージしながら確認しましょう。紙に四角形を書いて、それを見ながら確認するとわかりやすいと思います。


次回は、この衝突判定の仕組みを使って、ショットと敵キャラクターの衝突判定をやってみます。プレイヤーキャラクターの放ったショットがヒットしたら、敵キャラクターが消滅するようにするのですね。
これでまた一段とゲームらしくなりますね。
お楽しみに。


■格言

衝突判定とは座標の重なりを調べること
位置と幅が重要
4つの方向全てで考える


シューティングゲームに限らず、どんなゲームにも応用できます。






Chapter.51 [ シューティングゲーム11:衝突の実体 ]

■キャラクターと衝突判定

前回は衝突判定について解説しました。衝突判定の基本については、前回の講座をキチンと理解していれば大丈夫です。

さて今回は、シューティングゲームに衝突判定を実装させ、実際に敵キャラクターを攻撃できるようにしてみます。
ただし、実際にコードを書き始める前に、『衝突判定とあたり判定の違い』について考えてみたいと思います。衝突判定と、あたり判定、このふたつはなんとなく同じような感じがしますが、実は結構違います。まずはこの違いから考えていきましょう。


さて、それでは衝突判定とあたり判定の違いとは何でしょうか? 一般的にはどう考えられているのかわかりませんが、少なくとも私は次のように区別しています。

衝突判定:
衝突しているかどうかを調べて判定すること。
つまり判定する作業のこと。

あたり判定:
キャラクターの実体を表す特定の部位。
実際のキャラクターの、衝突を判定する実体部分。

ちょっといい日本語が浮かびません……。イメージしづらかったらすいません。なんとなく察してください。

衝突判定とは、ふたつの物体が衝突しているかを実際に判定する作業です。いうなれば、そのロジックのことです。
それに対して、あたり判定とは、キャラクター自体が持っている実体部分のことです。先ほども書いたように、一般的にこのような違いが明確になっているのかはわかりません。ただ、本講座では、このように双方の違いを定義します。ここから先に読み進めていく過程では、この違いを理解したうえで読んでみてください。


■キャラクターの大きさと実体

さて、今回作成しているシューティングゲームでは、赤いプレイヤーキャラクターが登場しますね。そして、そのキャラクターのデータを扱いやすくするために、構造体(正式名ユーザー定義型変数)を利用してデータを管理していました。ここまでは大丈夫ですか? もし不安なら、構造体について以前解説した講座がありますので、そちらを参照してくださいね(Chapter43を参照)。

さらに、プレイヤーキャラクターの座標計算を行いやすくするために、定数を使って、キャラクターそのものがどのくらいの大きさなのかを、あらかじめ定義していましたね。

'■キャラクター 構造体宣言

Type Chara
    X As Single '横位置
    Y As Single '縦位置
    W As Single '横幅
    H As Single '縦幅
    Lif As Long 'ライフ
    Typ As Long 'タイプ
    Par As Long '汎用パラメータ
End Type


'▲定数宣言

Public Const Player_Size As Single = 9
'プレイヤーキャラクターの実際の大きさ

さて、今更こんなものを持ち出して、何が言いたいのかと思ってしまいますね。

それではキャラクター構造体をよく見て下さい。その中に『 W …… 横幅 』と『 H ……縦幅 』という要素がありますね。
でもこれって考えてみると少し変だと思いませんか? だって、プレイヤーキャラクターの大きさは、そのあと定数Player_Sizeとして宣言されていますよね。横幅と縦幅なんて、ようはキャラクターの大きさを表すわけですから、同じなんじゃないの、と思ってしまいます。

さぁ今回のポイントはここです。
なぜ、大きさを表す構造体の要素と、大きさを表す定数と、似たようなものがふたつあるのか。画像を使って説明しますので、その違いをよく理解してください。

まず、定数として宣言しているPlayer_Sizeは、プレイヤーキャラクターの大きさを表しています。これは言い換えると、キャラクターの表示に使われるイメージコントロールの、実際の大きさですね。座標計算に使いやすくするために、イメージコントロール本来の大きさの、半分の数値である9が代入されています。

280.gif

もし、前回解説した衝突判定を、この定数を使って計算したらどうなるでしょうか。
衝突判定は、2つの物体に、重なっている部分があるかどうかを判定します。もし、定数Player_Sizeを使って計算を行ってしまうと、次のような状態でも衝突していると判断されてしまいます。

281.gif

これは困った問題ですね。実際には、イメージコントロール同士は確かに重なっています。ですが、キャラクターの画像が必ずしもコントロールの大きさと一致していないために、これで衝突したことにしてしまうと、かなり不自然な感じになってしまいます。

そこで、先ほど登場した構造体の要素『 W ・ H 』が活躍してくれるわけです。
構造体の要素である W と H には、イメージコントロールの実際の大きさには関係なく、キャラクターの見た目どおりの横幅と縦幅を設定しておきます。そして、その数値を元に、衝突判定を行うようにするのです。
キャラクターの衝突判定は、イメージコントロールの大きさそのものを元に計算するのではなく、擬似的に作り出したあたり判定用の四角形を使って計算するのです。

282.gif

この画像は、構造体の要素 W と H を、それぞれ4に設定した場合のあたり判定を表しています。このようなキャラクターよりも一回り小さいぐらいの判定にしておけば、衝突判定が不自然に感じることはありません。
中央にある小さめの四角形で衝突を判断するので、例えばキャラクターの羽根の端っこあたりを敵のショットが通ったとしても、それで衝突とはならないのです。


■コードを修正する

さて、それでは実際にコードを修正して、プレイヤーの放ったショットが、敵キャラクターを攻撃できるようにしてみましょう。

今回修正するべき箇所は、次の部分になっています。

①敵の出現管理プロシージャを修正
②プレイヤーのショットを管理するプロシージャを修正


それではまず①からです。
敵キャラクターの生成を行うプロシージャでは、現在のところ、敵の生成と同時に、定数Enemy_Sizeをそのまま W と H に代入しています。
このままだと、実際には当たっていないように見える距離でも、敵キャラクターの衝突判定が起こってしまう可能性があります。そこで、この部分に、ひとまわり小さい値を設定しておき、敵キャラクターの実体部分を少し小さくしておきましょう。
それが、以下のコードの①の部分にあたります。

Sub Enemy_Begin(X As Single, Y As Single, Typ As Long)

    Dim L As Long
    
    For L = 0 To 9
        With Enemy_Data(L)
            If .Lif = 0 Then
                .X = X
                .Y = Y
                .W = 5 '①
                .H = 5 '①
                .Typ = Typ
                .Lif = 1
                .Par = 0
                With UserForm1.Controls("Enemy" & L + 1)
                    .Visible = True
                    Select Case Typ
                        Case 1
                            .Picture = UserForm1.Enemy_s1.Picture
                        Case 2, 3
                            .Picture = UserForm1.Enemy_s2.Picture
                    End Select
                End With
                Exit For
            End If
        End With
    Next
    
End Sub

これは簡単ですね。
要素 W と H に、5を設定するように変更するだけです。

さて続いてはプレイヤーキャラクターの放ったショットの処理を修正します。
こちらは結構行数が多いですので注意してください。

前回解説した、衝突判定のやり方が理解できていれば、必ずコードを見ただけで仕組みがわかるはずです。一見して文字数が多いので、ちょっとわかりにくいかもしれませんが、やっていることは前回の内容と全く同じです。

Sub P_Shot_Action()

    Dim L As Long
    Dim LL As Long '①

    For L = 0 To 4
        With P_Shot_Data(L)
            If .Vis Then
                .Y = .Y - 6
                If .Y < -5 Then .Vis = False
                For LL = 0 To 9 '②
                    If Enemy_Data(LL).Lif > 0 Then
                        If .X - 2 < Enemy_Data(LL).X + Enemy_Data(LL).W Then
                            If .X + 2 > Enemy_Data(LL).X - Enemy_Data(LL).W Then
                                If .Y - 2 < Enemy_Data(LL).Y + Enemy_Data(LL).H Then
                                    If .Y + 2 > Enemy_Data(LL).Y - Enemy_Data(LL).H Then
                                        .Vis = False '③
                                        With Enemy_Data(LL)
                                            .Lif = 0 '④
                                        End With
                                        Exit For '⑤
                                    End If
                                End If
                            End If
                        End If
                    End If
                Next

                With UserForm1.Controls("P_Shot" & L + 1)
                    If P_Shot_Data(L).Vis Then
                        .Left = P_Shot_Data(L).X - Shot_Size
                        .Top = P_Shot_Data(L).Y - Shot_Size
                    Else
                        .Visible = False
                    End If
                End With
            End If
        End With
    Next

End Sub

ここで重要となる部分を、順に説明していきます。
緑色のコードで書かれている部分が、今回追加された部分です。

まず①ですが、ここで新しくプロシージャレベルの変数をひとつ宣言しています。
この変数は、敵との衝突判定を行う際に使います。あとで出てきますので、宣言しておくのを忘れないようにしましょうね。

次は②です。
ここでは全ての敵キャラクターとの衝突判定を行うために、For文を使って繰り返し処理を実行しています。大切なのは、存在していない敵キャラクターとの衝突判定をしても意味がないので、最初に『If Enemy_Data(LL).Lif > 0 Then』という条件分岐がかかっているという点です。
要素Lifが0の場合は、敵キャラクターは画面上に存在していません。ですから、この条件分岐を使って、現在敵キャラクターが表示されているかどうかを調べているのですね。
存在していない敵キャラクターとの衝突判定は、無駄な処理が発生するだけでなく、なんにもない場所で勝手に衝突が起きてしまうなど、不具合が発生する元となります。十分気をつけましょう。

続いて③。
ここでは、衝突が起きていた場合に、ショット自体を消してしまうために、要素VisFalseを代入しています。画面の外に消えてしまった場合と同じ扱いにするのですね。

④では、敵キャラクター自体を消してしまうために、要素Lifを0にしています。こうしておけば、キャラクター自体が存在していないことになるので、結果的に画面上から敵キャラクターが姿を消します。
Enemy_Actionという名前のプロシージャ内で、要素Lifの値を元に、イメージコントロールの表示、非表示を切り替えています。ですから、ここでは要素Lifに0を設定するだけでOKです。

そして最後に⑤の部分。
ショットが敵キャラクターと衝突していた場合、ここで『Exit For』として、繰り返し処理を抜けておきます。
これをやっておかないと、繰り返し処理によって全ての敵との衝突判定が行われるので、場合によっては同時に複数の敵に着弾することができてしまいます。1発のショットで、同時に2体の敵を撃破する、なんてことが起こったりするわけです。
もちろん、そのような仕様のゲームを設計している場合は別ですが、今回は1発のショットでは1体の敵キャラクターしか倒せないようにしておきます。


さて、以前までに完成しているコードを、今回の内容に修正してみてください。すると、プレイヤーの放ったショットが敵キャラクターにヒットした場合に、敵キャラクターが非表示になるようになったはずです。

つまり、プレイヤーキャラクターが攻撃する仕組みが実装できたわけです!
これで、とりあえずシューティングゲームという枠組みの、基本部分が完成したことになります。これから先、同様の考え方を使って、敵キャラクターがこちらを攻撃するようにする処理などを追加していきます。そうすれば、とりあえず遊ぶことができるシューティングゲームが完成です。
もう一息です。じっくりで構いません。しっかり理解を深めながら今回の講座内容をしっかり習得してください。


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


■格言

衝突判定とあたり判定は異なる概念
見た目どおりに判定が行われるようにしておく


これでかなりゲームらしくなりますね。






メールフォーム

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

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

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

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ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。