Baldur's Gate II AI スクリプト概要

BG2のAIスクリプトの構造等について説明します。


  1. 基本構造
  2. スクリプトの基本構造は以下のようなものです。
    IF
    	条件
    THEN
    	RESPONSE #100
    		アクション
    END
    
    この場合、条件が成り立てば100%の確率でアクションを実行します。
    アクションや条件は書き並べるとこで複数指定することもできます。
    条件の前に!をつけると逆条件(条件が成り立たなかった時に成立)が指定できます。
    あとはこれを色々組み合わせ、条件やアクションを色々工夫することで複雑な行動をさせることができます。
    IESDPにほぼ全ての機能及び説明が網羅されていますので参考にしてみてください。

  3. 複雑な条件
  4. 条件が複雑で多岐にわたる場合の説明です。
    例えば、条件Aは必須であるが条件Bか条件Cか条件Dはどれかを満たせば良い時は以下のようになります。
    IF
    	条件A
    	OR(3)
    		条件B
    		条件C
    		条件D
    THEN
    	RESPONSE #100
    		アクション
    END
    
    この場合、条件Aが成り立ち、かつ条件B〜Dのどれか一つが成り立てば100%の確率でアクションを実行します。

    これらは入れ子にする事はできませんので、物によっては複雑な書き方をする必要も出てきます。
    例えば、条件Aが成立した場合はそれだけで良いが、条件Bと条件Cが両方成立してる場合でも良しとする等はどうでしょうか。
    つまりif( A || (B && C))という場合です。
    IF
    	OR(2)
    		条件A
    		条件B
    	OR(2)
    		条件A
    		条件C
    THEN
    	RESPONSE #100
    		アクション
    END
    
    条件Aが成立していれば、2箇所のOR(2)はどちらも成立しますね。
    また、条件Aが不成立でも条件Bと条件Cが両方成立してる場合も2箇所のOR(2)はどちらも成立します。
    このように、複雑な条件も全て単純なOR文の列記に書き下せばOKです。

  5. アクションの分岐
  6. 条件が成立した際にとるアクションは、確率指定で分岐させることができます。
    IF
    	条件
    THEN
    	RESPONSE #25
    		アクションA
    	RESPONSE #25
    		アクションB
    	RESPONSE #25
    		アクションC
    	RESPONSE #25
    		アクションD
    END
    
    この場合、条件が成り立てば1/4の確率でアクションA〜Dのどれかを実行します。
    必ず合計が100になるようにする点を注意してください。
    合計100の必要は無く各数値同士の比率で分岐するようです。
    一定確率で何もさせたくない場合は、アクションとしてNoAction()を指定します。

  7. スクリプトフローの継続
  8. 以下のスクリプトを見て下さい。(条件やアクションはわかり易くする為に日本語表記しています)
    IF
    	敵発見
    THEN
    	RESPONSE #100
    		メッセージ表示「敵がいる」
    END
    
    IF
    	敵発見
    	敵との距離が遠い
    THEN
    	RESPONSE #100
    		飛び道具装備
    END
    
    IF
    	敵発見
    	敵との距離が近い
    THEN
    	RESPONSE #100
    		近接武器装備
    END
    
    このスクリプトを実行するとどうなるでしょうか。
    スクリプトは上から順に流れて行くので、敵が視界に入るとまず「敵がいる」とメッセージを表示します。
    そして次に、敵との距離に応じて武器を装備するように見えますが、実際にはそうなりません。
    ただひたすらに「敵がいる」と繰り返すだけです。
    なぜこうなるのかというと、条件が成立して何かを実行するとスクリプトは再び先頭に戻って処理を再開するからです。

    先頭に戻らずに処理を継続させるにはContinue()を使います。
    IF
    	敵発見
    THEN
    	RESPONSE #100
    		メッセージ表示「敵がいる」
    		Continue()
    END
    
    IF
    	敵発見
    	敵との距離が遠い
    THEN
    	RESPONSE #100
    		飛び道具装備
    END
    
    IF
    	敵発見
    	敵との距離が近い
    THEN
    	RESPONSE #100
    		近接武器装備
    END
    
    これで、(相変わらず喋りつづけますが)きちんと武器を装備するようになります。
    Continue()は非常に便利で多用しますので覚えておくと良いでしょう。

  9. 変数とタイマー
  10. 単純にその場の状況を判断して何らかの行動を起こすだけなら不用ですが、凝った事をさせようとするとどうしてもお世話になるものです。
    これらを用いて4の例をもうちょっとスマートにしてみましょう。
    // A.戦闘状態突入
    IF
    	変数「戦闘状態」が0
    	敵発見
    THEN
    	RESPONSE #100
    		メッセージ表示「敵がいる」
    		変数「戦闘状態」に1をセット
    END
    // B.臨戦体勢維持
    IF
    	変数「戦闘状態」が1
    	!敵発見
    THEN
    	RESPONSE #100
    		タイマー「様子見中」に10をセット
    		変数「戦闘状態」に2をセット
    END
    // C.臨戦体勢中に敵再発見
    IF
    	変数「戦闘状態」が2
    	敵発見
    THEN
    	RESPONSE #100
    		変数「戦闘状態」に1をセット
    END
    // D.戦闘終了
    IF
    	変数「戦闘状態」が2
    	タイマー「様子見中」が経過した
    THEN
    	RESPONSE #100
    		変数「戦闘状態」に0をセット
    END
    // E.射撃武器に持ち替え
    IF
    	敵発見
    	敵との距離が遠い
    	変数「射撃武器」が0
    THEN
    	RESPONSE #100
    		飛び道具装備
    		変数「射撃武器」に1をセット
    END
    // F.近接武器に持ち替え
    IF
    	敵発見
    	敵との距離が近い
    	変数「射撃武器」が1
    THEN
    	RESPONSE #100
    		近接武器装備
    		変数「射撃武器」に0をセット
    END
    
    どうでしょうか。
    現在の状態を変数「戦闘状態」として管理しています。 また、戦闘終了まである程度の猶予時間を設ける為にタイマー「様子見中」を入れてみました。

    A:「戦闘状態」が0の時敵を発見したらメッセージを発して「戦闘状態」を1にします。
    B:「戦闘状態」が1で敵が居なくなったらタイマー「様子見中」を10秒にセットし「戦闘状態」を2にします。
    C:「戦闘状態」が2の時敵を発見したら「戦闘状態」を1にします。
    D:「戦闘状態」が2のままタイマー「様子見中」が経過したら「戦闘状態」を0にします。
    E:敵を発見し、距離が遠く、「射撃武器」が0なら飛び道具を装備して「射撃武器」を1にします。
    F:敵を発見し、距離が近く、「射撃武器」が1なら近接武器を装備して「射撃武器」を0にします。

    視界に敵が入ると「敵がいる」とメッセージを発します。
    敵が物陰に入ったり魔法で消えたりで視界から居なくなっても、10秒間は臨戦体勢を維持しその間は再度発見してもメッセージは発しません。
    10秒間敵が居ない状態が続くと戦闘終了とみなして初期状態に戻ります。

    また、武器は現在どちらを装備しているかを変数「射撃武器」として保存してみました。
    これを条件に用いることで、既に最適な武器を装備しているのにも関わらず装備を繰り返す事によるそれ以降へのスクリプトフローのブロックを防げます。

    もう一点、4の例にあったContinue()が消えていることにお気づきでしょうか?
    書き方を工夫してフローのブロックが起きないようにすればContinue()無しでも大丈夫です。

    実際のスクリプトはこんな感じになります。
    コンパイルして動作を確かめてみるのも良いと思います。