プロが教える店舗&オフィスのセキュリティ対策術

Excel VBA について質問です。

VBA のプログラムを実行中に 一瞬フリーズすることがときどきあります。その原因や対処方法がわかれば教えていただきたいと思います。

VBA で作っているのはゲームです。ジャンルで言えばリズムゲーム(いわゆる「音ゲー」)なので、プレイ中に一瞬フリーズするだけでもミスが発生してしまうため困っています。

フリーズする時間は 100 ~ 300 ミリ秒程度です。フリーズする頻度はプレイ時間 数十秒 ~ 数分 に 1 回程度です。2 ~ 3 曲プレイするとそのうち 1 回はフリーズするといった感じです。フリーズしている間はGetAsyncKeyStateでキー入力のタイミングを正しく読み取れませんし、画面の更新が止まります。

なのでフリーズがあまりにも頻発しすぎるという程ではなく、ゲームとしてまともに遊べる程度なのですが、フルコンボや理論値を狙うガチの音ゲーマーにとっては、おそらく かなり困るのではないかと思います。(このゲームは完成次第インターネット上で公開するつもりです。)

フリーズは恒常ループの処理(プレイヤーがゲーム終了の操作をしない限りは無限に繰り返される、プレイ中に常に実行される Do ~ Loop文 内の処理)のどこかで発生しています。それ以外(ゲームを起動・終了するときに1度だけ実行する処理など)では、断言できませんが おそらく発生していないっぽいです。

プログラムの内容は、量が膨大すぎてここにコードをそのまま書くことはできませんが、恒常ループで頻繁に実行する処理としては、以下に挙げる処理を除いては、基本的に変数、配列、ユーザー定義関数(Function プロシージャ)、Sub プロシージャ の呼び出し、If文、For文などを組み合わせた「計算」しか行なっていません。

・wmp_obj.Controls.currentPosition の値の取得
wmp_obj は Object 型の変数で、ワークシート上に配置した Windows Media Player コントロールを指しています。このゲームでは、背景で動画を再生しながら、音ゲーに必要な要素である「音符」などが背景に重なって表示されるというスタイルになっています。『BeatTube』のようなイメージです。
この処理は1フレームに1回行なっています。なのでフレームレートが60FPSであれば、1秒間に60回 currentPosition を読みにいっているということになります。

・Windows API の BitBlt、StretchBlt によるビットマップ画像の描画
恒常ループに入る前に、CreateCompatibleDC で事前に生成したオフスクリーンを経由してダブルバッファリングをしています。
SetWindowLong、DrawMenuBar、SetLayeredWindowAttributes を使用してタイトルバーなどを非表示にし、特定の色を透過したユーザーフォームをワークシートの Windows Media Player コントロールにピッタリ重なるように配置して、そのユーザーフォームに「音符」などの画像を描画しています。

・Windows API の mciSendString による音の再生
音ゲーなので、「音符」を操作するタイミングで効果音を鳴らす必要があります。難易度の高い譜面では、1秒間に10回以上 mciSendString を呼ぶこともあります。
恒常ループに入る以前の準備段階で、音声ファイルを読み込む際に mciSendString にコマンドの文字列の最後で "type MPEGVideo" を指定しています。読み込むファイルの形式はWAV形式で、非圧縮です。

・Windows API の GetTickCount、GetAsyncKeyState、Sleep の使用

・DoEvents

先述のとおり、恒常ループではこれらの処理を除き、「計算」しかしていません。プロパティの参照、メソッドの実行(セル、ワークシート、ブックの操作など)も、恒常ループ内で1フレームごとに実行する処理としては存在しません。

wmp_obj.Controls.currentPosition の値の取得、BitBlt、StretchBlt、mciSendString に関しては、実行前・後に GetTickCount を取得してその差分から所要時間を計算して、何回かに1度、妙に時間がかかるようなことがないか調べてみましたが、実際に一瞬フリーズしたときであっても、これらの処理の所要時間は常にほぼ1ミリ秒未満で測定不能なほど早く、おそらくフリーズの原因は別にあるのではと思っています。

しかし、恒常ループ(ループ1回分)の先頭と最後で GetTickCount を取得してその差分を計算すると、通常時はほぼ0に近いですが、一瞬フリーズしたときには、確かに 100 ~ 300 ミリ秒程度の数字が出ます。なので、絶対にどこかの処理が遅くなっているのでしょうけど、ソースコードが膨大すぎて、片っ端から時間を計測してまわるくらいならここで質問したほうが楽じゃね? となったわけです。

Application.EnableEvents = False
とか、
Application.Calculation = xlCalculationManual
というような、「VBA 遅い」でネット検索したら出てくるような方法も、このケースでは関係ないだろうと思いつつも念のためやってみましたが、案の定これでは一瞬フリーズすることは防げませんでした。VBA高速化テクニックとしては定番の
Application.ScreenUpdating = False
も、やはりダメです。ScreenUpdating は WMP コントロールや ユーザーフォームでの BitBlt などに対しては無効ですし、そもそも画面の更新を止めてしまってはゲームとして成り立ちません。

何か特定の操作をしたときだけフリーズするわけでもありません。一定時間ごとではなく、ランダムなタイミングでフリーズするように感じられます。なので、ブックの自動保存機能によるフリーズではないと思います。

ゲーム開始時に1回だけ譜面のデータであるテキストファイルをストレージから読み込む動作があります。また、動画のデコードをするために、WMP がストレージから動画ファイルを自動的に読み込みます。なので、ゲーム中、ストレージとの通信はありますが、タスクマネージャーからディスクの使用率などを確認しても、処理が重そうな感じはしません。譜面や動画といった、ゲームに必要なデータはすべて OS がインストールされている Cドライブではなく Dドライブに保存しています。

ウイルス対策ソフト等がパソコンのパフォーマンスを占領しているわけでもありません。

開発環境は以下のとおりです。ノートパソコンです。

VBA:Microsoft Visual Basic for Applications 7.1
Excel:Excel 2019 (Office 2019 64ビット版)
Windows Media Player:Windows Media Player 12
OS:Windows 10 Home
CPU:AMD Ryzen 7 3700X
GPU:NVIDIA GeForce GTX 1660 Ti (外付けではありません。)
RAMの容量:32 GB (Excel が確保する量は多くても300MBといったところです。)

アイドル時では、CPU使用率2%以下、GPU使用率0%、ブックを保存しているDドライブの使用率も0%です。

新品で買ったばかりのパソコンで、アプリケーションもそんなにたくさんはインストールしていません。

他のパソコンにも同じブックを保存して VBA を走らせてみましたが、一瞬フリーズする現象は起きます。ですが、それでも 測定結果からフリーズ時には 恒常ループ に確実に時間がかかるとはいえ、根本的な原因は Excel 以外のところにある可能性も否定できないと思います。

とりあえず、現在のところ苦肉の策として、FPSがディスプレイのリフレッシュレートを大幅に超えないように VBAで Sleep関数 等を使用して FPS制限をかけ、一定時間あたりに実行する処理の量自体を減らしてフリーズが発生する頻度を少しだけ下げる程度の対策しかできていません。

そもそも、Excel でゲームを作っているという時点で、このようなことは贅沢な悩みかもしれませんけど、「Excelでできている異色なゲーム」というこだわりがありますから、全く別の言語で開発すればよくね? という話は今回はナシでお願いします。

とにかく、理想的には対処法などが存在するなら知りたいです。あと、そもそも原因・対処法ですらないことや、些細なことでも構いませんから、アドバイスや、思ったことなど何でもいいので回答よろしくお願いします。

A 回答 (1件)

こんにちは、


>そもそも原因・対処法ですらないことや、些細なことでも構いませんから、アドバイスや、思ったことなど何でもいいので回答よろしくお願いします。
> 一瞬フリーズ 又は、処理の遅延についてですが、
リソースによる遅延なのか、割り込み処理などによる遅延なのか、原因を特定する必要があるように思えます。 

ご理解されていると思いますが、PCの上にOSの上にOffice、、Excelのような状態を考えると、OSレベルでの割り込み処理やApplicationレベルでのそれを検証する必要があるかも知れません。
windowsイベントビューアーで事象が起きた時のログなどを確認してみてはいかがでしょう。また、Applicationレベルでのアドインやセキュリティソフト、プリンタードライバーなどとのやり取りも関係あるかも知れません。
セーフモードで実行可能な処理であれば、確認してみても良いかもしれません。
さらに、Excelのオプションにあるもので不要な機能をすべてOFFにするとか、スレッドを増やしてみるとか、、
Windows Media Player コントロールのMedia Playerの設定も少し気になるところですね。64bit Officeを使用した事が無いので他にも処理の遅延を起こす原因があるかも知れません。

残念ながら、再現できる環境が無いので、検証が出来ません。
ゲームをアプリケーションとして確立するなら、Excelの不要な部分を起動せず、出来るだけ上流で起動し他のアプリケーションの起動を止めて実行できるようにするのが良いのかな、、出来るかは、判りませんが。
へんてこな事を言っている私は、趣味の範疇しか知識が無く、無学ですが
お言葉に甘えさせて頂きました。
    • good
    • 0
この回答へのお礼

こんにちは。貴重なコメントありがとうございます。

あれから いろいろと試行錯誤をしてみました。指摘していただいた点について、できる限り確認等してみましたが、最終的には、「一瞬フリーズするのを無くす方法は謎」という結論にたどり着きました。

ゲームをインターネット上で公開し、だれでも無料でダウンロードして手軽に遊べるようにする予定なので、プレイヤーが Excel の設定等をプレイに適するように変更する手間をかけさせたくないことと、様々なPC環境でプレイされるであろうことを考慮して、最終的には、「一瞬フリーズ」が起きるという前提で設計することにしました。

Excel VBA からは、1ループごとにGetTickCountを取得することで「一瞬フリーズ」が発生したことをほぼ確実に検知できるので、処理落ち発生時の音ゲーの音符の操作ミスを補償し、自動的に「PERFECT判定」にするように仕様変更をしました。念のため補償回数には上限を設定しています。

また、こういうシステムをもつ音ゲーは他には(私の知識の範囲では)存在しないので、自分のパソコン等の性能の問題で処理落ちに困っていたゲーマーの助けになる……と言うと大げさですけど、この「補償」は需要が無くはない機能だろうと思って、これはこれでヨシと思っております。

結局のところ「一瞬フリーズ」を無くすことはできませんでしたが、この現象を避けるのではなく向き合って、上記のとおりの仕様変更をすることでこの問題を解決することができたのは、「一瞬フリーズ」を無くそうとしてもそれが不可能または非常に困難であることを知る必要がありました。

あなたの指摘が、それを知ることの助けになったと思い、感謝しております。

お礼日時:2021/09/21 18:36

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています