GO BACK

Visual Basic教室 その6


「イメージの描画と動き」


■イメージの重ね合わせについて


 今回は、グラフィックの描画について考えてみましょう。といっても、先にやったような円や四角を描くようなものではなく、グラフィックソフトなどで作成したイメージを表示する方法についてです。

 単純にイメージを表示するだけなら、ImageやPictureBoxのPictureプロパティでイメージファイルを設定すれば、それだけで画面に表示することができましたね? そこで今度は、「イメージを重ね合わせる」ということについてからやってみましょう。

 複数のイメージを重ね合わせるという処理は比較的多用されるものです。例えばゲームの類いを作るときには、背景の上にキャラクタのグラフィックを重ねて表示する、というようなことが必要となりますね。

 イメージ重ね合わせのもっとも簡単な方法は、「背景をフォームに設定し、キャラクタの類いをImageコントロールに設定して配置する」というものです。これは誰でも思い付くことでしょう。

 が、実際にこれで重ね合わせを行なうとちょっとした問題に遭遇します。それは、「キャラクタの周囲の余白が表示されてしまう」ということです。単純にグラフィックを描画したファイルをBMPなどのファイルで保存した場合、周囲の余白が「白」としてそのまま表示されてしまいます。

 この回避方法としてもっとも簡単なものは、「透過GIFを使う」ということでしょう。GIFフォーマットでイメージを作成する場合、特定の色を透過色として設定することができます。周囲の余白部分の色(つまり白)を透過色として設定したGIFファイルを使えば、そのまま余白が抜けた状態で重ね合わせることが可能なのです。




■イメージをクリックして移動する


 重ね合わせができたら、今度はキャラクタを動かしてみましょう。「動かす」といっても、とりあえずは位置を移動させるという程度のことから考えてみます。といっても、あるいは既に皆さんの中には方法がわかってる!という人もいるかも知れませんね。今までの講座を頭に入れていれば、想像できるものです。

 コントロールの表示されている位置というのは、それぞれプロパティとして用意されています。今までプロパティパレットを何度となく調べていれば、それに気づいたことでしょう。それは「Left」と「Top」というものです。

 Leftは、そのコントロールがフォームの左からどれだけ離れているかを示すものです。そしてTopは、コントロールがフォームの上からどれだけ離れているかをを示します。どちらも、単位はTwipとなります。

 ですから、このLeftとTopの値を変更すれば、その瞬間にコントロールは表示位置を変えてしまうのです。

 これは、実際に試してみれば一目瞭然でしょう。――フォームの上にImageを1つ作成して下さい。そして表示させたいイメージファイルをPictureに設定しておきます。

 それができたら、フォームの地の部分をダブルクリックしてソースコードウィンドウを開きます。そしてコードを作成するわけですが、今回は「フォームの何もない部分をクリックしたら動く」というサブルーチンを利用してみることにしましょう。

 ソースコードのウィンドウをよく見ると、上の部分に2つのコンボボックスがあるのがわかりますね? このコンボボックスには、左に現在作成されているコントロールの一覧が、右には選択したコントロールに用意されているイベントの一覧が表示されるようになっています。

 この2つのコンボボックスの左側から「Form」を、右側から「MouseDown」というのを選んで下さい。ウィンドウにそのためのサブルーチンの基本構文が書き出されます。これを元にしてサブルーチンを記述します。

Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
  Image1.Left = X - (Image1.Width \ 2)
  Image1.Top = Y - (Image1.Height \ 2)
End Sub

 サブルーチンの最初の定義部分がずいぶんと長いですが、とりあえず説明は後にしておきましょう。そして記述したら、プロジェクトを実行して下さい。フォームをクリックすると、その地点にImageが瞬時に移動するのがわかりますよ。




■MouseDownと位置に関する注意点


 それでは、サブルーチンを見てみましょう。まず、MouseDownというサブルーチンの働きからです。これは、マウスボタンを押し下げた瞬間に発生するイベントにより呼び出されるものです。

 このサブルーチンは、()の部分がずいぶんと長いですね。ここには、カンマ(,)で区切った4つの項目が記述されています。それぞれの項目を見ると、


《変数名》 As 《変数の型》


――こんな感じで書かれていることがわかりますか? この()部分は、既に登場しましたが「パラメータ」と呼ばれる部分です。パラメータは、そのサブルーチンが呼び出された際に、一緒に受け渡される値とそれを収める変数について定義するものです。つまり、「このサブルーチンが呼び出されるときに、この型の値がこの変数に収められて一緒に渡されますよ」ということをここで定義してあったわけです。

 では、このMouseDownで受け渡される4つの値とはどういうものなのでしょうか。ここでざっと整理しておきましょう。


Button As Integer
サブルーチンを呼び出すきっかけとなったマウスボタンを示す値が収められる。左ボタンが押されたなら1、右ボタンなら2、もし中央ボタンがあるマウスでまん中が押されていたなら4となる。

Shift As Integer
ボタンを押したときに、同時にモディファイキーが押されていたかどうかを示すもの。シフトキーが押されていたら1、Ctrlキーが押されていたら2、Altキーが押されていたら4となる。同時にいくつかのキーが押されていた場合は、それらの数字の合計になる。例えばシフトキーとAltキーが押されていたら5になる。

X As Single
クリックした地点がそのコントロールの左からどれだけ離れた地点にあるかを示すもの。Singleというのは小数の値を収める型の一種。

Y As Single
クリックした地点が、そのコントロールの上からどれだけ離れた地点にあるかを示すもの。


 ざっとこれでパラメータの意味がわかりましたね? これがわかれば、サブルーチンで行なっていることはすぐにわかります。ここでは以下の2行の命令が実行されていました。

  Image1.Left = X - (Image1.Width \ 2)
  Image1.Top = Y - (Image1.Height \ 2)

 クリックした地点からImage1の幅の2分の1を引いたところにImage1を移動する、という作業をしていたのです。なぜ「幅の2分の1を引く」のかわかりますか? それは、Image1の中心がクリックした地点にくるようにするためです。単純にXとYにLeftとTopを変更すると、クリックした地点にImage1の左上の角がくるようになります。そこで、縦横の幅の半分だけ左上方向にずらして表示位置を決定させているのですね。

 さて、これで説明はおしまい、全て解決!…といきたいところですが、実はもうちょっと説明しておきたいことがあります。今回のサブルーチンは、確かにうまく動きました。が、実はうまく動いてくれないこともあるのです。

 それは「スケールモードの違い」が問題となる場合です。このMouseDownでは、XとYという変数にクリックした位置の情報が受け渡されました。では、その値の単位は何でしょうか? 今回はどうやらTwipであるようです。が、問題なのは、いついかなる場合もTwipになるわけではない、ということです。

 フォームのプロパティをよーく見てみると、その中に「ScaleMode」というものがあることがわかると思います。これは「スケールをどんな単位で計測するか」を示すものです。MouseDownのXとYは、実はこれで設定された単位で計測した値を受け渡すのです。

 ですから、これがTwip以外のものになっていたなら、値は全く違うものになってしまうというわけです。実をいえば、フォームには「ScaleModeで設定した単位で位置や大きさを計測する」ためのプロパティが用意されています。それは「ScaleLeft」「ScaleTop」「ScaleWidth」「ScaleHeight」といったものです。これらを使えば、スケールモードがなんであっても正しく位置や大きさを設定できます。

 が、これらは全てのコントロールに用意されているわけではありません。例えば、Imageコントロールには通常のTopやLeftしかないのです。従って、こうしたコントロールを扱う場合は、計測の単位がTwipに統一されているかどうかをチェックしてやる必要があるのです。このことは、位置や大きさを扱う際の基本注意事項として覚えておくようにして下さい。




■タイマーで動かす


 何かのコントロールを動かすという場合、もちろんユーザーがクリックしたりした操作に応じて動かすこともできますが、「ユーザーが何もしていないときに動いている」というようなプログラムを書く必要が生じることもあります。こういう場合はどうすればいいんでしょうか?

 このようなときは、「タイマー(Timer)」と呼ばれるコントロールを利用するのが一般的です。このTimerは、一定時間おきに定期的にサブルーチンを実行する働きを持つコントロールなのです。

 Timerは、「見えないコントロール」です。フォームの上にTimerを作成すると、小さなアイコンが表示されたコントロールが作られますが、これはプログラムを実行すると画面には表示されなくなります。つまり「機能だけで画面表示を持たないコントロール」なのです。

 このTimerの使い方はとても簡単です。Timerに用意されている2つのプロパティを利用して動作を制御するのです。その2つのプロパティとは以下のものです。


「Enabled」――タイマー機能が動作中かどうかを示すもの。これがTrueならば動作していることを示し、Falseならば停止していることを示す。これを変更することでタイマーをON/OFFする。

「Interval」――タイマー機能でサブルーチンを呼び出す間隔を示す。これはミリ秒(1000分の1秒)単位となる。例えば「100」とすると0.1秒ごとに呼び出される。


 では、実際にタイマーを利用して、先ほどのプロジェクトのImageを動かしてみることにしましょう。フォームの上にTimerを1つ作成してください。そして、「Enabled = True」「Interval = 500」とプロパティを設定します。

 それができたら、作成したTimer1をダブルクリックしてソースコードウィンドウを開きます。そしてサブルーチンを記述します。

Private Sub Timer1_Timer()
  X = Int(Rnd * Form1.Width)
  Y = Int(Rnd * Form1.Height)
  Image1.Left = X - (Image1.Width \ 2)
  Image1.Top = Y - (Image1.Height \ 2)
End Sub

 これは、ランダムにImage1を動かすサブルーチンです。Timerで呼び出されるサブルーチンは、その名もズバリ「Timer」という名前のものです。TimerコントロールのTimerサブルーチン(ああややこしい…)に実行したい命令を書いておくと、Intervalで設定した時間ごとに定期的にそれが呼び出され実行されます。

 ここでは、まずRndを使って、Form1の横幅・縦幅の乱数を作成し、それぞれXとYに設定しています。小数点以下を切り捨てて整数を得るのに「Int()」という関数を使ってみました。これは、


Int (数字)


――このように使います。こうすると、整数部分だけが得られるのです。前に1で割る方法を紹介しましたが、まあこういう関数もあるのだ、ということで一応紹介を兼ねて使ってみました。




■直接イメージを描画する方法


 これで一応、グラフィックを重ね合わせ、移動させることができるようになりました。が、実際に試してみると、この方法には限界があることがわかってきます。まず、動かすときに瞬間的にですが画面がちらついてしまいます。特にTimerのIntervalを短くして短時間に動かすと、かなりちらつきが目立ってきます。また、ImageはPictureBoxの上に重ねたりできないなど、使い方にもやや制限がされてしまいます。

 こうした問題は、「コントロールを使ってそれぞれのイメージを表示し、動かす」という方法をとる以上、避けられないことです。ということは、逆に考えるなら、コントロールで表示せず、直接イメージを描画する命令などがあれば、こうした問題は回避できそうです。

 実は、イメージを表示させるコントロール(Form,PictureBoxなど)には、そのためのメソッドが用意されています。それは「PaintPicture」というものです。このメソッドを使うと、そのコントロール内にダイレクトにイメージを描画することができます。

 では、ちょっと試してみましょう。先ほどのタイマーを使ったプロジェクトを手直ししてみることにします。

 まず、Image1の「Visible」プロパティをFalseにします。このVisibleというプロパティは、画面に表示するかどうかを示すもので、Trueならば表示し、Falseならば非表示となります。これで、Image1を見えない状態にしておくのです。

 これらの変更ができたら、サブルーチンを書き換えて、PaintPictureを使った形に修正してみましょう。

Private Sub Timer1_Timer()
  X = Int(Rnd * Form1.Width) - (Image1.Width \ 2)
  Y = Int(Rnd * Form1.Height) - (Image1.Height \ 2)
  Form1.PaintPicture Image1, X, Y
End Sub

 一応、これでよさそうですね。では、実際にプロジェクトを動かしてみましょう。――すると、どうもなんだか予想とはかなり違う状況になってますね…。まず、せっかくGIFを使って背景色を透過するように作っておいたというのに、PaintPictureではそれが活かされなくなってしまいます。また、描画をする度に新しいイメージが表示されてしまい、前のイメージが消えないので、画面にどんどんイメージが増殖してしまいます。




■マスクを使った重ねあわせ


 もっとも重要な問題点は、「重ね合わせたときにキャラクタの周囲の余白をどうやって取り除くか」でしょう。これが今回の最大のポイントです。

 これは、「マスク処理」という手法を使って取り除くのが一番確実です。マスクとは、そのグラフィック部分だけを黒く塗りつぶしたグラフィックのことです。このマスクを使って特殊な重ね合わせ方式による描画をすることで、余白が取り除けるのです。

 これにはちょっとした下準備が必要です。――まず、表示するキャラクタのグラフィックを修正する必要があります。キャラクタの周囲の余白を全て黒く塗りつぶしたグラフィックと、キャラクタのグラフィック部分を黒く塗りつぶし、周囲の余白部分を全て白く塗りつぶした状態のグラフィック(これがマスク)の2つを用意して下さい。

 そして、フォームの上にもう1つImage(これがImage2となる)を作成します。こちらも、VisibleはFalseにして隠しておきましょう。そして、Image1にキャラクタのグラフィックを、Image2にマスクのグラフィックをそれぞれ設定します。

 上記の図のような状態になりましたか? これで、2つのImageを組み合わせて描画する下準備ができました。後は、サブルーチンを書き直すだけです。

Private Sub Timer1_Timer()
  X = Int(Rnd * Form1.Width) - (Image1.Width \ 2)
  Y = Int(Rnd * Form1.Height) - (Image1.Height \ 2)
  Form1.PaintPicture Image2, X, Y, , , , , , , vbSrcAnd
  Form1.PaintPicture Image1, X, Y, , , , , , , vbSrcPaint
End Sub

 これで完成です。プロジェクトを実行してみましょう。今度は、ちゃんと周囲の余白のないグラフィックがどんどん描かれていくはずです。

 これは一体どういうことなのかというと、秘密はPaintPictureの最後のパラメータにあります。「vbSrcAnd」とか「vbSrcPaint」というやつです。

 PaintPictureというのは、実をいえば非常にたくさんのオプションパラメータを持っているのです。全てのパラメータを記述すると、こんなに長くなります。


PaintPicture picture, x1, y1, width1, height1, x2, y2, width2, height2, opcode


 最初のpictureは、描画するグラフィックのことですね。その後のx1, y1, width1, height1は描画する場所と幅、x2, y2, width2, height2は描画元になるグラフィックの位置と幅を示します。つまり、「あるグラフィックの特定の部分だけを、別のグラフィックの特定の部分に描画する」ということができるようになっているのです。それで、描画元と描画先のそれぞれの位置と大きさを指定するパラメータがあるのですね。

 問題は最後のopcodeというパラメータです。これは、描画の方式を示すものです。単純にコピーして描画するだけでなく、さまざまな演算処理をして描画をさせることができるのです。

 この描画方式については、全て理解しようとするとかなり大変です。ここでは、「まずマスクをvbSrcAndで描き、次にキャラクタをvbSrcPaintできれいに重ねて描くと、周囲の余白がきれいになくなった状態で描かれる」ということだけ覚えておきましょう。

 というわけで、これで問題は解決しました。――おっといけない、まだ「グラフィックが増殖する」という問題が残ってました。では、これもなおしておきましょう。

Private Sub Timer1_Timer()
  X = Int(Rnd * Form1.Width) - (Image1.Width \ 2)
  Y = Int(Rnd * Form1.Height) - (Image1.Height \ 2)
  Form1.Refresh
  Form1.PaintPicture Image2, X, Y, , , , , , , vbSrcAnd
  Form1.PaintPicture Image1, X, Y, , , , , , , vbSrcPaint
End Sub

 これで修正完了です。PaintPictureの前に「Form1.Refresh」というものがありますね? このRefreshというのは、画面を初期状態に戻すメソッドです。つまり、これを実行すると、その前にPaintPictureで描画してあったものはクリアされ、元の背景だけの状態に戻るというわけです。

 以上、描画に関する一通りの説明を行ないましたが、わかったでしょうか? 最後の「マスク処理」は、ちょっと高級なテクニックですから、ある程度前半の基礎がわかってから挑戦してみて下さい。


GO NEXT


GO HOME