MessageBoxの活用(前編)「MessageBoxを連続表示する」
Oblivionのスクリプティングの中で意外に手強いのがダイアログボックスを表示する「MessageBox」です。独特のルールがあって直感的でないので注意が必要です。前編では「MessageBox」を順番に表示するスクリプトをご紹介します。
その前に、「MessageBox」に関して「Construction Set 日本語 help」の「Script→Script Tutorial」に詳しい解説があるので、読んだことがないという方はそちらをお読みになってからのほうがいいと思います。
直感的にやってもできない
やりたいことは「あるオブジェクトを調べる→1つ目のMessageBoxを表示→プレイヤーがOKを押す→2つ目のMessageBoxを表示→プレイヤーがOKを押す→終了」です。直感的にやると以下のようになります。
ScriptName MessageBoxX2 Begin OnActivate ;アクティベートされたら実行 MessageBox "1つ目のMessageBox" MessageBox "2つ目のMessageBox" End
「MessageBox」を2つ並べただけです。で、実際にこれを実行すると2つ目しか表示されず、OKを押すと終了してしまいます。「MessageBox」が複数実行されている場合、最後のものだけ表示されるようです。
「MessageBox」のイメージとして「表示中はプレイヤーがOKや選択肢を選ぶまでスクリプトが一時停止する」というのがあると思いますが、実際はプレイヤーの動きに関係なくスクリプトの最後まで実行されます。この場合、同一フレームで「MessageBox」を2つ表示するスクリプトになっているのです。片方だけしか表示されませんが。
妥協案としては、片方を「MessageBox」に、もう片方を「Message」にして同時に表示することです。しかし「Message」は2、3秒で消えてしまうので問題になることもあるでしょう。
スクリプトに一手間が必要
完璧に解決するには、少々面倒ですがスクリプトを一工夫する必要があります。つまり、「1つ目を表示→プレイヤーがOKを押したことを感知→2つ目を表示」という手順が必要で、一手間かかります。
ScriptName MessageBoxX2 short state ;進行度の管理用 short button ;OKの感知用 Begin OnActivate ;アクティベートされたら実行 If (state == 0) set state to 1 ;フラグをON、再実行を防ぐ MessageBox "1つ目のMessageBox" EndIf End Begin GameMode ;プレイヤーが近くにいて読み込まれている限り、毎フレーム実行される If (state == 0) ;フラグがOFFならReturnで即終了 Return ElseIf (state == 1) ;Activateされたら処理開始 set button to GetButtonPressed If (button == -1) ;OKを押されるまでは-1が代入されるため、Returnで終了し続ける Return Else ;OKを押されたらこちらに分岐する MessageBox "2つ目のMessageBox" set state to 0 ;忘れずフラグを戻す EndIf EndIf End
一気に複雑になってしまいました。しかし内容は決して難しくないです。順番に見ていきましょう。
「OnActivate」ブロックは、アクティベートされた時に実行されます。まず「state」は0なので、「state」に1を入れてフラグをON(スイッチを入れるみたいな意味)すると同時に再実行を防ぎ、1つ目の「MessageBox」を表示します。
その下に「GameMode」ブロックがあります。これはプレイヤーが近くにいてデータが読み込まれている間は毎フレーム実行されるものです。毎フレーム処理なので、できる限り処理は軽くする必要があります。
アクティベートされる前は「state」は0なので、「Return」によってすぐに終了されます。この程度なら負荷はほぼありません。
アクティベート後は「state」には1が入っていてそちらに分岐されます。「GetButtonPressed」はプレイヤーの選んだ選択肢を返す関数で、1個目の選択肢を選べば0、2個目なら1が返されます。そして最も重要なのが、入力待ちの間はずっと-1を返し続けるということです。
「MessageBox」で入力待ちをする場合はプレイヤーが選択するまで必ず「Return」し続ける必要があります。上の例の場合、-1である限り「Return」で即終了です。あと注意が必要なのは、プレイヤーが選択肢を選んだそのフレームに、すぐに値が反映されるとは限らないことです。とにかく、「-1ならReturn」を使って監視し続ける必要があります。
プレイヤーがOKを押した場合、「GetButtonPressed」は-1でなく0(恐らく)を返すため「Else」に分岐して、2つ目の「MessageBox」を表示します。今回はこれ以上の処理が必要ないので、「state」を0に戻して、スクリプトによる処理は終了です。
なんでダイアログボックスを2つ表示するだけでこんなに面倒なんだ…と憤るのが普通だと思いますが、「開発者はそれぞれの関数の仕様として、ヴァニラを実現するための最小限の機能の実装に留めた」という説を聞き、なるほど、そうかもと感心しました。余計なことを実現するためには、余計な手間がかかる…これは仕方ないと思います。一手間加えれば自由度はいくらでもありますしね。
ちなみに、「Return」の盲点(というか私がハマった点)が、「このスクリプトの処理を終了する」というところです。「このブロックの処理を終了」ではありません。
スクリプトは上から下に処理されていきます。仮に一番上に「GameMode」ブロック、下に「OnActivate」ブロックがあるとして、「GameMode」ブロック内で「フラグがOFFだったら即Return」としている場合、そのスクリプトは毎フレーム即Returnされ、アクティベートされても先に「GameMode」ブロックでReturnされてしまうために「OnActivate」ブロックが実行されることは永遠にありません。
何かと便利な「Return」ですが、スクリプト全体の構造を考えないと困ったことになることがあるのでご注意下さい。