クエストを使って監視・処理を共有化する方法
クエストといえば何かのストーリーやイベントを想像するのが普通ですが、クエストスクリプトには便利な使い方があります。
クエストスクリプトとは
クエストスクリプトとはクエストに添付されるスクリプトのことで、クエストが走っている間はデフォルトで5秒毎に実行されます。プレイヤーがどこにいようが、何をしていようが確実に定期的に実行されるため非常に便利です。
スクリプトを走らせるためだけのクエストを作る
スクリプトを使いたいだけならクエストの中身は全く必要なくて、ただクエストスクリプトを添付すればいいだけです。添付するスクリプトは「Script Type」を「Quest」にして下さい。

「Start Game Enabled」はゲーム開始時からデフォルトでクエストを有効にする設定です。これにチェックを付けない場合は何かのスクリプトで「StartQuest クエスト名」を実行してクエストを走らせる必要があります。止める場合は「StopQuest クエスト名」です。
監視用のスクリプトの例: 早くBy Azuraしたい皇帝陛下に5秒毎に怒られるスクリプト
メインクエストのアーティファクトを渡す場面。ここでプレイヤーが「Azura's Star」を持っていて、Martinにまだ渡していない時にお怒りのお言葉を頂戴するという無意味なクエストスクリプトを想定してみます。
クエストスクリプトの中身はこんな感じ。
ScriptName MartinWantByAzuraScript Begin GameMode ;5秒毎に実行される If (Player.GetItemCount AzurasStar == 1) Message "Martin「そのAzura's Starをさっさとよこせ!」" EndIf End
実際はクエストの進行度とか色々なものを考慮する必要があるのでこんなシンプルじゃないですが、とりあえずこれだけで5秒毎に皇帝陛下の有り難いお言葉を頂けます。
ちなみに全く無駄ですが、もしも上のクエストスクリプトと同じ処理をプレイヤーが所持している何らかのアイテムに添付したスクリプトで実現するとしたらこんな感じになります。
ScriptName MartinWantByAzuraScript float Timer Begin GameMode If (Player.GetItemCount AzurasStar == 0) ;負担軽減のために持ってなかったらすぐに終了 Return ;Returnはこのフレームの処理を終了する関数 EndIf set Timer to Timer + GetSecondsPassed ;タイマーでカウントして If (Timer > 5) ;もしも5秒過ぎたら set Timer to 0 ;タイマーをリセット Message "Martin「そのAzura's Starをさっさとよこせ!」" EndIf End
5秒毎のクエストスクリプトと違い、普通のスクリプト(オブジェクトスクリプト)は毎フレーム実行されるためにタイマーを使う必要があります。こういった処理を省けることもクエストスクリプトの利点です。
4〜6行目は負担軽減用の処理で、こういったものを設けなくても動くのですが、毎フレーム実行されるようなスクリプトの場合は負担軽減のために「この条件なら無視」というのを先に判定してReturnさせるといいです。
1回だけ実行するスクリプトの例: スペルやアイテムを追加するとか
よくMODで、スペルや装備品などをゲーム開始と同時にプレイヤーに追加したい場合がありますが、その処理は非常に簡単です。
クエスト名が「TuikaQuest」だとして、「Start Game Enabled」(ゲーム開始と共に有効)にチェックを入れて、クエストスクリプトは下記のように。
ScriptName TuikaQuestScript Begin GameMode Player.AddItem TuikaSoubi 1 Player.AddSpell TuikaSpell StopQuest TuikaQuest ;クエストが終了されるので、このスクリプトは二度と実行されない End
下記でも可能です。DoOnce変数により1度だけ処理します。上記に比べメリットは何もないですが…。
ScriptName TuikaQuestScript short DoOnce Begin GameMode If (DoOnce == 0) ;5秒毎にIf文の判定が行われるためムダ set DoOnce to 1 ;変数で1度だけ実行とするよりも、 Player.AddItem TuikaSoubi 1 ;クエストを終了させたほうが早いし効率的 Player.AddSpell TuikaSpell EndIf End
処理を共有化するスクリプトの例: 引換券を消費して金を得る
良い例が思いつかなかったので申し訳ないのですが、引換券を持っていてこれを使うと引換券を消費して金を貰えるというスクリプトを考えてみます。
まずアイテム側のスクリプト。
ScriptName HikikaeKen500GoldScript Begin OnEquip ;このアイテムを使った時に処理開始 set HikikaeKenQuest.Gold to 500 ;クエスト変数Goldに500を代入して StartQuest HikikaeKenQuest ;クエストを開始させる RemoveMe ;最後に自分を消す End
次にクエスト側のスクリプト。クエスト名は「HikikaeKenQuest」とします。
ScriptName HikikaeKenQuestScript short Gold ;クエスト変数。他からアクセスできる Begin GameMode Player.AddItem 0000000f Gold ;0000000f = 0f = お金 MessageEX "やったね! 商品券と引き換えに%gゴールド手に入れたよ!" Gold StopQuest HikikaeKenQuest ;このクエストを停止させる End
これで引換券を使った際にクエストが始動してお金を貰えるという処理ができます。
補足をしておくと、「クエスト名.変数名」とすることで、クエストで定義している変数に外部からアクセスできます。同様に、「リファレンス名.変数名」とすることで、そのリファレンスのローカル変数にアクセスできます。
Message(Box)EXでの変数の値の表示についてはMessageBoxなどで変数の値を表示する方法をどうぞ。使いこなすと途轍もなく便利です。
利点は重複を省いたことによる管理の楽さ
こういった形をとる利点は、商品券が500Gold、1000Gold…のように複数になった場合でも、重複が減って管理が楽だという点です。
商品券を複数用意したとしても違いは「金額」だけであって、金を増やす処理やメッセージの表示などは変わりません。そこで、アイテム側としては金額だけを定義して、呼び出すクエストスクリプトで金を入手する処理を行えば極力重複が省けるわけです。
逆に、もしも全ての商品券のスクリプトにお金の入手の処理まで書いてしまった場合は、後になって処理の仕方を変えたいなんて時には、全ての商品券のスクリプトを修正しなくてはならなくなります。重複はできるだけ省くべきです。
ちなみにクエストが止められてもそのクエストスクリプトが実行されなくなるだけであり、クエスト変数の値はそのまま残って外部からの操作(Gold変数への代入など)も普通にできるのでその点は心配要りません。
「fQuestDelayTime」でクエストスクリプトの実行間隔を変更する
デフォルトのクエストスクリプトの実行間隔は5秒毎なので、上記の場合はアイテムを使用後すぐにお金を入手したいのに遅延が発生してしまい困ります。
クエストスクリプトの実行間隔は、そのクエストスクリプト中で定義した「fQuestDelayTime」変数で変更できます。
ScriptName HikikaeKenQuestScript float fQuestDelayTime ;クエストスクリプトの実行間隔
このようにクエストスクリプト中で定義して、「set クエスト名.fQuestDelayTime to 実行間隔」を実行すればOKです。同じクエストスクリプトの中で変更してもいいですし、他のスクリプトからでも問題ありません。0.01くらいに設定するとほぼ遅延がなくなります。
おまけ1: GetSecondsPassedについて
タイマーなどによく使う「GetSecondsPassed」ですが、これの使い方は熟知しておくべきです。
「GetSecondsPassed」は、1つ前のフレームから現在のフレームまでの経過時間を返します。例えばもしもFPSが1だった場合は1秒が1フレームなので、GetSecondsPassedを使った場合には「1」が返されます。FPSが100だった場合は1秒が100フレームなので「0.01」が返されます。
タイマーとして使う場合は基本的に、「set Timer to Timer + GetSecondsPassed」とすればタイマーにカウントされていきます。そして十秒後に処理を開始したいなら、「If (Timer > 10)」とすればOKです。その際に「set Timer to 0」でタイマーをリセットしておくのを忘れないで下さい。あと、GetSecondsPassedは小数を使うので、変数にはfloatを使って下さい。
おまけ2: クエストは関数としては使いづらい
処理の共通化が可能なクエストスクリプトですが、処理をさせてその結果を受け取るという使い方には適していません。クエストスクリプトの実行には少なくとも1フレームのタイムラグがありますし、結果を返すにしてもクエスト変数などに反映するしかないからです。
OBSE v0018以降からユーザ定義関数というものが使えるようなのでそれを活用すべきかと思いますが、私はよくわかりません。