VBAアクションゲーム?Excelで動かそう!世留と仙人第4章

   


第4章 3箇条その3 同期Wait処理〜後編〜


第4章で作成されるxlsファイル

世留少年はいつになく難しい表情で考え込んでいます。

   




世留

パソコンの性能に関係なく一定の速度で表示させる・・・・。Sleepは一定の時間、処理を止めるか。うーん難しいなあ。0.5秒に1回表示させるっていうことは、ループ1回をぴったり0.5秒で終わらせればいいってことだから・・・やっぱりSleep使っても駄目じゃないかなあ。処理にどれくらい時間がかかるか計れればいいんだけど。仙人は頭の体操とか言ってたけど、何か他のAPIがあるんじゃないかなあ?
 
仙人  (首を傾げながら入ってくる)
見間違いかのお・・・

世留 あ、お帰りなさい。ハイネはどうでした?

仙人 小さい玉のようなものを転がしておって、どうもマウスボールに見えたんじゃが。気のせいかのお。それよりお前さん、いい知恵は浮かんだかい?

世留 仙人、いろい考えてるんですが、どうしてもうまくいかないです。他に同期処理に必要なAPI関数があるんじゃないですか?

仙人 (あっさりと)
使うのは朝に教えたGetTickCountだけじゃよ。うまくいかんかのお。

世留 GetTickCount?!聞いたことありませんよ、そんなの。

仙人

世留 教えてもらってません!

仙人 ・・・?。そうっだったかのお?。ほっほっほ、それは悪いことをしたわい。では、今から教えよう。

世留 まったくもう、考えて損しちゃったよ。

仙人 まあまあ世留君、今のお前さんのように、わからないなりに考えて、「ひょっとしたら新しい関数があるかもしれない」と思い至るのはよいことじゃよ。それだけ深くコードを考えたことになるからのう。では・・・・

  (さらさらと書き始める)

  Declare Function GetTickCount Lib "kernel32" () As Long  'Windows起動後経過時間取得API
Sub Test
 MsgBox GetTickCount
End Sub

世留 またあっさりしていますね。これで何が表示されるのですか?

仙人 実行してみなされ。

世留
わ、数字が表示された。仙人仙人、これは何の数字ですか?

仙人 (咳払いして)
コホン。GetTickCount関数はのう、Windowsが起動されてからの経過時間を取得できるのじゃ。単位はミリ秒単位。10000と表示されたのなら、10秒ということ、つまり、パソコン立ち上げてから10秒経ったというわけじゃ。

世留 へえー。便利ですねえ。でもこれが同期処理に使えるんですか。

仙人 使えるとも。何せミリ秒単位で時間を取得できるのじゃからのう。

世留 そうか、このGetTickCountを使えば、1回のループにかかる時間を計測できる。・・・0.5秒からと処理時間の差をSleepで指定してやれば・・・・仙人、できましたよ!

仙人 さ、さ、コードを書いてみなされ。

世留 えーと、まずループの開始時間を変数に収納する必要があるから・・

  (考えながら書き出す)

  Dim StartTime as Long  ’ループスタート時間収納変数
Dim TempTime as Long  ’処理にかかった時間収納変数

Do While GameFlag = True
  StartTime=GetTickCount  

  ’ここに処理が入る

  TempStart=Gettickcount-StartTime  ’現在の時間からスタート時間を引く=処理時間
  Sleep (500-TempStart)   
’処理させたい時間から実際にかかった時間を引いて、Waitとする。

Loop

  できた。考えてたことがコード化できましたよ!

仙人 ほほおーよく考えたのお。実際の処理にかかった時間を計測し、あるべき時間との差を埋めるためにSleepで休ませる。世留君、なかなかのものじゃ。

世留 やったあ。じゃあ、早速ゲームに組み込んでみよう・・・

仙人 いや、ちょっと待ったほうがよいぞ。これではパソコンが止まってしまう恐れがある。

世留

仙人 お前さんのコードはズバリ言って不完全じゃ。その例の場合、もし処理に1秒かかったらどうしゃ。Sleepで指定する時間がマイナスとなってしまうぞ。

世留 ・・・・ほんとだ、・・・じゃあ、if文で分岐させて、・・・駄目だ、仙人、頭がこんがらがってきました。

仙人 ほっほっほ。世留君、よく頑張ったのう、ここまで考えられれば合格じゃ。アクションゲーム3箇条目はわしが教えよう。しっかりと覚えるんじゃぞ。

  (さらさらとコードを書く)
  Declare Function GetTickCount Lib "kernel32" () As Long 'Windows起動後経過時間取得API
Dim StartTime as Long  ’ループスタート時間収納変数

Sub strat()
 Dim i As Integer
 Dim RoopTime as Integer
 RoopTime=500
 GameFlag = True
 Do While GameFlag = True
  StartTime=GetTickCount
 
   ’ここに処理が入る
  Do While GetTickCount-StartTime < RoopTime 
’Wait:処理がRoopTime未満ならループ
    
’ここに処理Aが入る(1ループ中処理し続けるもののみ抜粋:キー入力判定など)
  Loop 
 Loop
End Sub

仙人 こんな感じしゃ。

世留 ・・・・・

仙人 なに、簡単じゃろ。ループの中に、もう一つループを入れるのがミソじゃ。RoopTimeが制御したい時間、この場合500ミリ秒=0.5秒で指定しておる。ループの開始時に時間をStartTimeに収納しておく。処理が終わったときに、Waitループに入るぞ。その時点の時間とStartTimeの差が0.5秒以上なら、すぐにこのループから抜ける。そうでなければ、つまり0.5秒未満で処理が終わってしまっていた場合、0.5秒に達するまで、Waitループの中をくるくる回り続けるという訳じゃ。ただ空のループを回り続けるだけじゃその間、キー入力を受け付けなくなるのでよくない。コードは長くなってしまうが、その間にもキー入力判定部分は入れておくのじゃ。

世留 そう言われてみれば簡単ですねえ。確かに、処理時間が0.5秒未満の場合は、このループから抜けることは出来ない。これが3箇条目ですね!

仙人 そうじゃ。アクションゲーム作成の定石、最後の三箇条目「同期Wait処理」じゃ。

世留 ・・・でもね、仙人、二つ疑問があるんです。

仙人 なんじゃ?

世留 一つは、Waitループの中、当たり判定で同じ処理を二度も書いてますよね。これ何か無駄のような気が。

仙人 たしかに無駄じゃな。もっと効率よく、サブルーチンに分けるとよいかもしれん。じゃが今回は同期Wait処理を確実に学んで欲しいからこのままにしておこう。

世留 もう一つは、これで確かに0.5秒単位で制御できそうですが、処理自体に0.5秒以上かかった場合は、これ駄目ですよねえ。1ループに0.5秒以上かかっちゃいますよ。

仙人 ほほほ。良いことを言うのう世留君。でもそれができたらプログラマーはこの世に必要なくなってしまう。

世留 え?

仙人 あはは、考えてもみなさい。1秒かかる処理をWait処理と称して数行コードを追加するだけで0.5秒に短縮することができたら、それはそれは凄いことじゃぞ。そんな夢のようなコードがあるのなら、是非わしも教えて欲しいもんじゃ。

世留 そうか、だから同期Wait処理っていうんですね。Waitですもんね。

仙人 そうじゃ、同期高速化処理ではないからのう。じゃがな、1秒かかる処理を0.5秒にする努力は必要じゃ。それはその処理を徹底的に見直すことで可能になることもある。VBという言語に共通のセオリーもあれば、もっと広義にプログラムのアルゴリズム(処理方法)の鉄則もある。

世留 せ仙人・・・そんな難しいことをいきなり言わないで下さい。もう3箇条で頭が一杯です。

仙人 すまんすまん。お前さんが作りたいワークシート上でのゲームなら、ある程度の定石もあるから、そこはぼちぼちやっていこうかのう。さて、忘れんうちにWait処理を組み込んで見なされ。

世留 あ、はい

(目を白黒させながら、コードを書き始める)

  Option Explicit
Declare Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Long
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Declare Function GetTickCount Lib "kernel32" () As Long 'Windows起動後経過時間取得API
Public GameFlag As Boolean

Sub strat()
 Dim i As Integer
 
Dim StartTime As Long 'ループスタート時間収納変数 
 GameFlag = True
 Dim RoopTime As Integer
 RoopTime = 500

 Do While GameFlag = True
  StartTime = GetTickCount
  Range("A1").Value = Int(Rnd * 10)

  If GetAsyncKeyState(16) <> 0 Then
   If Range("A1").Value = 7 Then
    MsgBox "大当たり!"
    GameFlag = False
   ElseIf Range("A1").Value = 3 Then
    MsgBox "当たり!"
   GameFlag = False
   Else: MsgBox "はずれ!"
   End If
  End If

  Do While GetTickCount - StartTime < RoopTime 'Wait:処理がRoopTime未満ならループ
   If GetAsyncKeyState(16) <> 0 Then
    If Range("A1").Value = 7 Then
     MsgBox "大当たり!"
     GameFlag = False
    ElseIf Range("A1").Value = 3 Then
     MsgBox "当たり!"
    GameFlag = False
    Else: MsgBox "はずれ!"
    End If
   End If
  Loop

 Loop
End Sub


  ふー、やっとできた。

仙人 よくできたよくできた。これでアクションゲーム3箇条は完璧じゃぞ。世留君、1点だけ留意しておかなければいけないことがある。このプログラムはのう、ただセルの数字を書き換えるものとは全然違う、まさにアクションゲームじゃ。その違いがわかるかのう。

世留 ミリ秒単位で制御しているから、ですよね。

仙人
ふむ。もちろんそれはそうじゃ。じゃが決定的に違うことがあるんじゃよ。このプログラムはのう、イベントリブン型から脱却しておる。
世留

仙人 このコードを実行すればわかるが、実行中パソコンは少しも休まず、ひたすらこのプログラムを実行し続けているのじゃ。通常のExcelVBAソフトはのう、こんな動きはしない。ユーザーがマウスをクリックした等のイベントに応じて、プログラムの一部分が走り出す、言い換えればユーザーが何もしなければ何も始まらず、ユーザーの操作を待ち続けている。これをイベントトリブン型のプログラムといってのう、世留君にはちと早いが、仕事で使うようなExcelのプログラムは殆どがこの方式じゃ。

世留 ・・・・・

仙人 イベントトリブン型の場合は、ユーザーの操作を取りに行く必要はないんじゃ。この辺は難しくなるが、VBA、あるいはWindowsが監視してくれていてのう、操作に応じた処理だけを書けばいい。いわば「待ち」のプログラムじゃ。お前さんの作ったプログラムは違うぞ。ひたすら自分で走り続け、ユーザーの動きを自発的に取りに行く「攻め」のプログラムじゃ。

世留 攻め・・ですか。

仙人 いかにも、「攻め」じゃ。イベントトリブン型からの脱却とはそういことなのじゃ。世留君、このコード実行中パソコンが熱くなるじゃろう?それはパソコンが一時も休まず、お前さんの書いたコードを実行しているからじゃ。場合によってはのう、パソコンを休ませてやるコードも追加する必要がでてくるかもしれんぞ。

世留 パソコンを休ませてやる・・・・

仙人 そうじゃ。これは面白いぞ。パソコンを休ませるのも働かせるのもプログラム次第。ユーザーの操作を受け取るのもプログラム次第・・。イベントリブン型のプログラムにはあり得ない、自分のプログラムで全てを制御していく醍醐味じゃ。失敗するとパソコンは暴走するぞ。わははは。

世留 仙人、それは笑い事じゃないですよ。そんな危険なプログラムは作りたくないです。

仙人 何の、もう作っているではないか。大丈夫じゃよ。壊れはせんから。暴走したら止められるようにコードを書けばよいのじゃからな。

世留 そんなこともできるのですか。もう頭がくらくらしてきましたよ。お腹もすいてきちゃった。

仙人 おお、話しているうちに時間を忘れていたようじゃ。日も暮れてきたし、どうじゃ世留君、晩飯も食べていきなされ。何なら泊まっていってもいいぞ。

世留 ほんとですか?じゃあ、お家に電話してみます。

仙人 わしから話そうか・・・・・ん?いい匂いがしてきのう、ばあさん気を利かしてすき焼きを用意しておるな。ささ世留君、一緒に居間に行こうかのお。


動かすメモ5 ※API関数GetTickCount

※Windowsの起動後経過時間をミリ秒単位で取得する。

Declare Function GetTickCount Lib "kernel32" () As Long 'Windows起動後経過時間取得API
Sub Test
 MsgBox 
GetTickCount
End Sub

動かすメモ6 ※同期Wait処理

※ループ1回当たりの処理時間をミリ秒単位で指定する(例:0.5秒)

Sub strat()
 Dim i As Integer
 Dim RoopTime as Integer
 RoopTime=500
 GameFlag = True
 Do While GameFlag = True
  StartTime=GetTickCount
    ’ここに処理が入る
  Do While GetTickCount-StartTime < RoopTime 
’Wait:処理がRoopTime未満ならループ
    
’ここに処理Aが入る(1ループ中処理し続けるもののみ抜粋:キー入力判定など)
  Loop 
 Loop
End Sub


VBAアクションゲーム?Excelで動かそう!世留と仙人第4章