2019年7月23日

【Papyrus.psc】周囲のNPCやフォロワーに対してスクリプトを配布する方法

- Papyrusスクリプトの記憶を記録に残すシリーズその16 -

ボタンを押したりパワーを唱えたりしたとき、周囲にいるNPCに何らかの動作をさせる方法です。
範囲魔法を使ってスクリプトを配布します。


目次

  1. スクリプト配布用の範囲魔法を作成する
  2. スクリプトを作成する
  3. ボタンで範囲魔法を発動する
  4. パワーで範囲魔法を発動する
  5. フォロワーに限定してスクリプトを配布する
  6. 範囲魔法の確実性を向上させる

1. スクリプト配布用の範囲魔法を作成する

1-1. Magic Effect の作成

周囲にスクリプトを配布するには、何らかの方法で「見えない範囲魔法」を発動して、その魔法が当たったときの効果としてスクリプトを設定することになります。

魔法の構造は、実際に発動するものとなる「Spell」と、その効果内容を設定する「Magic Effect」とに分かれています。まずは Magic Effect から作っていきましょう。

CKのオブジェクトウィンドウで Magic > Magic Effect を開き、右クリックから「New」を選んで新規作成します。

新規作成画面が開きますので、以下のように項目を設定します。

  • ID … 任意のID
  • Name … ゲーム内では表示しないので、自分が分かりやすい名前
  • Effect Archetype … Script
  • Casting Type … 撃ち放し(Fire and Forget)
  • Delivery … 自己(Self)
  • Area … 効果範囲(フィート単位)
  • Casting Sound Level … Silent

「Area」は、プレイヤーを中心にスクリプトを配る範囲を指定します。この数値の単位はフィートです。30フィートだと 9.144m です。

効果音は設定しませんが、念のため「Casting Sound Level」を「Silent」にして、音が出ないようにしておきます。

後ほど、この Magic Effect に配布したいスクリプトを付けることになります。

1-2. Spell の作成

作成した Magic Effect を格納する Spell を先に作ってしまいましょう。
オブジェクトウィンドウで Magic > Spell を開き、右クリックから「New」を選びます。

新規作成画面が開きますので、以下のように項目を設定します。

  • ID … 任意のID
  • Name … ゲーム内では表示しないので、自分が分かりやすい名前
  • Type … 呪文(Spell)
  • Casting … 撃ち放し(Fire and Forget)
  • Delivery … 自己(Self)

「Casting」と「Delivery」は、設定する Magic Effect と同じにしておく必要があります。

上記の項目を設定したら、右側の「Effects」欄で右クリックして「New」を選択します。

すると、使う Magic Effect を選択する画面が出ますので、「Effect」欄で先ほど作成したものを選びます。

下図のようになれば、Spell の作成は完了です。

目次に戻る


2. スクリプトを作成する

2-1. スクリプト設定の追加

それでは、Magic Effect にスクリプトを設定しましょう。
作成した Magic Effect を再度開いて、右下にある Papyrus Scripts の「Add」ボタンを押します。

スクリプト追加画面が出ますので、「New Script」を選択します。

「Name」にスクリプト名を入れます。他のMODと被らないような名前にしてください。
「Extends」はすでに「activemagiceffect」と入力されているはずですが、一応確認してからOKします。

下図のようになれば、スクリプトファイルが作成されています。

2-2. スクリプトの編集

作成したスクリプトを編集していきます。
個人の環境設定によりますが、右クリックの「Edit Source」または「Open in External Editor」で開くことができます。

スクリプトの1行目はすでに入力されていますので、2行目以降に下記のコードを貼り付けます。

Event OnEffectFinish(Actor akTarget, Actor akCaster)
    If akTarget.IsWeaponDrawn() == false
        akTarget.DrawWeapon()
    EndIf
EndEvent

実行する内容は、例としてNPCが抜刀するようにしました。「akTarget」が周囲にいるNPCを指しています。実際にはここに配布したい処理を書いてください。

2-3. スクリプトのコンパイル

スクリプトを書いたら、メニューバーの「File」から「Save」を選びます。
「Compiler Output」欄に「Compilation succeeded.」の文字が出れば成功です。

これで、実行するスクリプトとそれを配布する範囲魔法が完成しました。
一旦CKで保存しておきましょう。

目次に戻る


3. ボタンで範囲魔法を発動する

3-1. ボタンの設置

今度は、範囲魔法を発動する方法を作ります。これは何をしたいのかによって様々だと思いますので、まずは例としてボタンを押して発動する方法をご紹介します。

セルビューウィンドウから、「WhiterunDragonsreach(000165A3)」をダブルクリックして、レンダーウィンドウにドラゴンズリーチを出します。

オブジェクトウィンドウで WorldObjects > Activator の中から「ImpButton01」を探し、レンダーウィンドウにドラッグすると、ボタンを設置することができます。

3-2. ボタンへのスクリプトの追加

設置したボタンをダブルクリックして Reference を開き、「Scripts」タブを表示してください。
「Add」を押し、範囲魔法のときと同様のやり方で新しいスクリプトを付けましょう。

ボタンに付ける際は、Extends が「ObjectReference」になっているはずです。

スクリプトが作成されたら、以下のコードを追記してコンパイルします。

Spell Property speDistribution Auto

Event OnActivate(ObjectReference akActionRef)
    speDistribution.Cast(akActionRef)
EndEvent

これで、ボタンを押したときに魔法を発動することができるようになりました。
ただし、「どの魔法を発動するのか」を指定しなければいけませんので、追加したスクリプトを選択して「Properties」を開きます。

「speDistribution」を選択して、右側の「Edit Value」を押します。

プルダウンから範囲魔法を選択します。頭文字をキーボードで打つと、楽に選択できます。

スクリプトでは、「Cast」関数を使ってプレイヤーに範囲魔法を唱えさせています。このとき、プレイヤーにその魔法を習得させておく必要はなく、プロパティに設定した魔法を自由に発動させることが可能です。

3-3. テストプレイでの確認

ここまでできたらCKで保存し、テストプレイしてみましょう。
NMM等で、作成したespファイルをロードオーダーに加えるのを忘れないでください。CKで保存しただけではロードオーダーに入りません。

正常に動作すると、「首長とプレイヤーを除く周囲のNPCが一斉に抜刀する」はずです。
今回のスクリプトでは首長のバルグルーフさんは抜刀しません。単純なスクリプト指示を出しただけでは、強いAI制御によって無効になるケースがあります。

もしかしたら、プレイヤーが抜刀してしまうなど、スクリプトの配布がうまくいかないこともあるかもしれません。
そのときは、Spell を開いて「Effects」を設定し直したり、「Type」や「Equip Type」を一時的に別のものにしたりしてみてください。

魔法がうまく動かないこのような現象は、作成時に何らかのエラーを発生させた場合に起こることがあります。
経験的には、「Type」等の設定を変更すれば直ります。直ったらまた元の設定に戻しても大丈夫なはずです。

目次に戻る


4. パワーで範囲魔法を発動する

4-1. パワー用の Magic Effect の作成

スクリプト配布の範囲魔法を発動するために、別の魔法やパワーを用いる方法です。
プレイヤーに専用パワーを覚えさせ、唱えたときに周囲にいるNPCへスクリプトを配布してみます。

オブジェクトウィンドウで Magic > Magic Effect を開き、新規作成します。

  • ID … 任意のID
  • Name … ゲーム内では表示しないので、自分が分かりやすい名前
  • Effect Archetype … Script
  • Casting Type … 撃ち放し(Fire and Forget)
  • Delivery … 自己(Self)
  • Magic item description … インベントリ画面で表示される説明書き

範囲魔法の場合とほとんど同じです。説明書きには、配布するスクリプトによって何が起こるか書いておきましょう。

4-2. パワーの作成

オブジェクトウィンドウで Magic > Spell を開き、新規作成します。

  • ID … 任意のID
  • Name … ゲーム内で表示するパワー名
  • Type … 天賦の術(Lesser Powers)
  • Casting … 撃ち放し(Fire and Forget)
  • Delivery … 自己(Self)
  • Menu Display Object … インベントリ画像
  • Equip Type … Voice
  • Effects … 作成した Magic Effect を設定

「天賦の術」にすると、1日に何度も使えるパワーになります。Equip Type の「Voice」とセットで指定しましょう。

Menu Display Object は、インベントリ画面で表示される画像です。「MAGINV」から始まるものを指定するときれいです。

4-3. パワーへのスクリプトの追加

パワー用の Magic Effect にスクリプトを追加し、以下のコードを追記してコンパイルします。

Spell Property speDistribution Auto

Event OnEffectFinish(Actor akTarget, Actor akCaster)
    speDistribution.Cast(akCaster)
EndEvent

ボタン発動のときと同様、プロパティには範囲魔法を指定してください。
このパワーを唱えると範囲魔法が発動し、周囲のNPCにスクリプトを配布します。

4-4. パワーの習得

パワーを作成したら、それをプレイヤーに覚えさせましょう。
方法はいくつかあって、MOD導入直後に自動的に覚えさせるか、石碑をアクティベートすると覚えられるようにするか、あるいは魔法書を作って置いておくか、ということが考えられます。

今回は、石碑をアクティベートする方法でやってみます。
オブジェクトウィンドウで WorldObjects > Activator の中から「WETempActivator」を探し、レンダーウィンドウへドラッグして配置します。

石碑には以下のようなスクリプトを付け、プロパティに覚えさせたいパワーを指定します。

Spell Property spePower Auto

Event OnActivate(ObjectReference akActionRef)
    If (akActionRef as Actor).HasSpell(spePower) == false
        (akActionRef as Actor).AddSpell(spePower)
    Else
        (akActionRef as Actor).RemoveSpell(spePower)
    EndIf
EndEvent

アクティベートするとパワーを習得し、再度アクティベートすると忘れられるようにしてみました。必要に応じて、メッセージを出したり音を出したりすると良いでしょう。

4-5. 範囲魔法を直接覚えさせない理由

このパワーを唱えたときの効果は、「範囲魔法を発動する」というだけのものです。
それなら、最初から範囲魔法を覚えさせれば良いのではないかと思われるかもしれません。

しかし、範囲魔法を直接唱えさせるようにしてしまうと、「そもそもスクリプトを配布するかどうか」を制御することができないのです。

例えば「特定の場所で唱えたときにしか効果が出ない」という設定にしたいとき、1段目のパワーの中でならスクリプトを配布する前にそれを一括制御できます。スイッチの役割です。

一方で2段目となる範囲魔法は、唱えた時点で周囲のNPCを検出し、スクリプトを配布しようとしてしまいます。
一応、Conditions やスクリプト内容で効果対象の制限はかけられるものの、各NPCに対してそれぞれ条件判定を行うことになります。

効果が出ないという点ではどちらも同じ結果ですが、余計な負荷をかけないようにすることを考えて、2段階の発動に分けています。

目次に戻る


5. フォロワーに限定してスクリプトを配布する

5-1. Conditions の設定

スクリプトを配布するNPCを限定してみます。例として、「雇用中のフォロワー」という条件を設定してみましょう。

範囲魔法の Magic Effect を開き、「Target Conditions」の欄で右クリックして「New」を選択します。

下図のように「GetPlayerTeammate == 1.0000」となるようにします。
こうすることで、範囲魔法の対象をフォロワーに限定することができます。

5-2. その他の Conditions

設定できる条件は非常に多岐にわたります。範囲魔法に使えそうなものをいくつかご紹介します。

Condition Function 用途
GetActorValue 体力などの Actor Value で判定。
GetIsID Form ID で判定。
GetIsRace 種族で判定。
GetIsSex 性別で判定。
GetItemCount 所持しているアイテム数で判定。
GetLevel レベルで判定。
GetPlayerTeammate 雇用中のフォロワーかどうかで判定。
GetRandomPercent ランダム判定。0から99で指定する。
「<=50」とすれば50%の確率で適用。
HasKeyword 設定されたキーワードで判定。
特に「ActorTypeNPC」を指定することで人間に限定可能。
IsEssential 不死属性かどうかで判定。
IsInList FormList に含まれるかどうかで判定。

6. 範囲魔法の確実性を向上させる

6-1. 範囲魔法の不確実性

スクリプトを配布する範囲魔法と、それを発動するためのボタンやパワーの作り方をやってきました。これで、範囲魔法の「Area」内にいるNPCにスクリプトが配布されるはずです。

しかし実際にテストプレイを繰り返すと、範囲内にいてもスクリプトが配布されないNPCが時折いることに気付きます。

これは範囲魔法全般で起こりうる現象で、一部のNPCをうまく捉えることができず、範囲内にいても効果が適用されないというものです。
ゲーム中はプレイヤーもNPCも動いており、他の処理も大量に走っている以上、効果対象の検出が不確実になるのは仕方のないことかと思います。

なんとなく「すぐ目の前にいる」NPCには範囲魔法がかかりやすく、「画面外のやや離れた距離にいる」NPCにはかかりにくい気もしますが、絶対ではありません。
この不確実性を少しでも改善できないか、最後に検討してみましょう。

6-2. 確実性の向上

範囲魔法が不確実だとしても、魔法の設定からそれを改善することは困難です。そのため、「何度か範囲魔法を発動する」という方法を取ります。
1度目で捉えられなかったNPCを、2度目以降で捉えようという作戦です。

「ボタンで範囲魔法を発動する」のところで使ったボタン用のスクリプトを、以下のように書き換えます。

Spell Property speDistribution Auto

Event OnActivate(ObjectReference akActionRef)
    speDistribution.Cast(akActionRef)
    Utility.Wait(1.0)
    speDistribution.Cast(akActionRef)
    Utility.Wait(1.0)
    speDistribution.Cast(akActionRef)
EndEvent

1秒間隔で3度重ね掛けするようにしてみました。
これでテストプレイを行うと、すぐには抜刀しなかったNPCでも、やや遅れて抜刀してくれるのがわかります。

この手法を使うときは、同じ指示を重ねて出さないよう、範囲魔法に条件判定を組み込んだ方が良いです。
今回の範囲魔法では、スクリプト内で If 条件を使って、抜刀中でないNPCにのみ抜刀指示を出すようにしてあります。

If akTarget.IsWeaponDrawn() == false
    akTarget.DrawWeapon()
EndIf

範囲魔法を重ね掛けするということは、それだけスクリプトを配ることでもあります。
処理内容の重要性と負荷のバランスを考えて、重ね掛けの回数と間隔を決めましょう。

目次に戻る


【Papyrus.psc】シリーズリスト

補足:私はスクリプトを書くのは好きですが、専門家ではありません。内容は creationkit.com の情報と個人的な経験を基にして書いています。どうかご参考程度にご覧ください。

トップに戻る

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。