2018年10月3日

【Papyrus.psc】スクリプトを用いたオブジェクトの配置方法 -検証編-

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

前回の 基本編 の続きです。今回は、オブジェクトの配置における特殊な仕様について書いていきます。


目次

  1. 出現位置と位置情報との連動
  2. テンプレートの存在
  3. 位置連動とテンプレートとの関係まとめ
  4. スクリプトでオブジェクトを配置する
  5. スクリプトでオブジェクトを固定する
  6. スクリプトで既存オブジェクトを再配置する

1. 出現位置と位置情報との連動

1-1. 位置が連動しない問題

セル内のオブジェクトの位置情報はXYZ座標で表され、「GetPosition」関数で取得できます。
例えばリンゴが1つ落ちていたとして、それがコロコロと転がって移動したとき、通常はXYZ座標も同時に変化します。リンゴの出現位置と、座標で表される位置情報が連動している状態です。
「当たり前じゃないか!」と言われそうですが、これがなんと連動しないケースがあるのです。リンゴは転がっているのに、座標値が全く変化しないというものです。

この話題については詳細に検証された記事がすでにあります。おばちゃんのスカイリムガイド様の「スカイリム室内装飾ガイド」です。実験編考察編続きの話 の3部作で、ゲーム内の挙動を様々なパターンで検証してくださっています。
簡単に言うと、武器以外のアイテムをドロップした後は、セルのロードを挟まないと、見えている位置と座標が連動しない という状態にあります。

ここで言うセルのロードとは、一度外に出るなどして、黒背景にレベルバー等が表示される画面を挟んでから元のセルに戻ってくることを指します。レベルバーが表示されない短時間の黒画面ではだめなのと、セーブデータのセーブ&ロードでもだめです。

この問題を如何にクリアするかということ、およびスクリプトを使ってどこまで解決できるのかということが、今回の主な話題です。

1-2. 位置を連動させる方法

位置が連動しない問題は、「同一のnifを使うオブジェクト」がすでにそのセル内にあるかどうかで対策が異なります。ここでは、初めてnifを出現させるところから順番に考えていきましょう。

まず、武器の場合は特殊なので先に挙げます。武器だけは、プレイヤーがインベントリからドロップした場合でも、最初から位置連動した状態で出現します。特に何もする必要はなく、掴んで移動させると座標も連動して変化します。

一方、その他のアイテムの場合は、位置連動しない状態で出現します。これをプレイヤーの行動で解決するには、前述の通りセルのロードを挟むしかありません。
スクリプトが使える場合は、そのアイテムを「Disable」した後に「Enable」すれば、位置を連動させることができます。実際のスクリプトでは間に「Wait」を入れた方が安定して動作するので、以下のように書きます。

objTarget.Disable()
Utility.Wait(0.001)
objTarget.Enable()

これだけで、objTarget に入れたオブジェクトは位置連動します。

また、スクリプトの「PlaceAtMe」でアイテムを直接出現させた場合も、原則として最初から位置連動します。例外もあるので後述します。

目次に戻る


2. テンプレートの存在

2-1. テンプレートとは

1つ目として配置したオブジェクトは、2つ目以降に配置するオブジェクトに影響を与えます。

1つ目として配置し、位置連動させたものを、ここでは「テンプレート」と呼ぶことにします。
ドロップ後にセルのロードを挟むか、スクリプトで「Enable」したものがそれに当たります。または「PlaceAtMe」で直接出現させたオブジェクトもそうです。

このテンプレートがセル内に存在する場合、2つ目以降のオブジェクトは、ドロップしたものであっても最初から位置連動した状態で出現します。
逆に1つ目が位置連動していない状態では、2つ目も連動しません。1つ目を飛ばして2つ目をテンプレート化することもできないので、1つ目をテンプレート化する作業を行うしかありません。

武器はやはり動作が特殊で、1つ目として出現させてもテンプレートになることはできないようです。
同じnifを使う武器と錬金素材を作成して検証しましたが、武器を1つ目としてドロップした場合は最初から位置連動しているものの、2つ目に錬金素材をドロップしても連動していませんでした。もし武器がテンプレートになるなら、2つ目の錬金素材は連動しているはずです。
さらに、1つ目の武器を出現させた後にセルのロードを挟み、テンプレート化する作業をしてから2つ目の錬金素材をドロップしてみましたが、その2つ目も連動していませんでした。
以上より、武器はテンプレートとして使えないと考えられます。

ちなみに、espによってあらかじめ配置されたオブジェクトがある場合は、それがテンプレートになってくれています。プレイヤーがドロップするのは2つ目以降になるので、最初から連動している状態になります。

2-2. 2つ目が連動しないケース

スクリプトの「PlaceAtMe」で出現させた1つ目のオブジェクトは最初から位置連動しており、テンプレートになっています。
しかし、2つ目も「PlaceAtMe」で出現させたときの挙動は、nifによって変わります。2つ目が連動する場合としない場合とがあるのです。
nifに全く詳しくないので予想を含みますが、食べ物や錬金素材など、元々ドロップできるアイテムのnifなら2つ目も位置連動し、クエストアイテムなどのドロップできないアイテムのnifを流用したものだと、連動しないような気がします。これは使うnifによって異なるので、ゲーム内で実際に確かめた方が良さそうです。

さらに、1つ目としてすでにプレイヤーがアイテムをドロップしていてテンプレート化していない場合、2つ目を「PlaceAtMe」で配置しても位置連動しません。
そのため、テンプレートとしてオブジェクトを配置したつもりが、実はそれが2つ目で連動していない、という事態になる可能性があります。
プレイヤーが1つ目をドロップしたかどうかを判定するのは困難なので、バニラのオブジェクトを「PlaceAtMe」で配置する際には注意が必要です。

目次に戻る


3. 位置連動とテンプレートの関係まとめ

以上を基に、同じnifを使っているオブジェクトを配置したときのパターンを検証してまとめてみました。
「PlaceAtMe」、「Disable」、「Enable」はスクリプトから行う動作、「ドロップ」はプレイヤーがインベントリから捨てる動作を指しています。
それぞれのパターンで、オブジェクトが位置連動するかどうかと、テンプレートになるかどうかをまとめています。

  1. セル内で1つ目のnifの場合
    • 「PlaceAtMe」で配置すると、どのオブジェクトも位置連動し、武器以外はテンプレート化。
    • 武器の「ドロップ」は、最初から位置連動するがテンプレートにはならない。
    • その他アイテムの「ドロップ」は、そのままだと位置連動せず、テンプレートにもならない。「Disable」「Enable」するか、セルのロードを挟めば、位置連動かつテンプレート化。
  2. セル内で2つ目のnifの場合
    1. 1つ目が武器のとき
      • 2つ目は、1つ目のnifを配置する場合と同様の動作になる。
    2. 1つ目が武器以外のテンプレート化されたアイテムのとき
      • 2つ目がドロップ可能アイテムなら、どの方法で配置しても連動する。
      • ドロップ不可アイテムのnifを流用している場合、連動しないものがある。連動しないものは、セルのロードを挟むと連動するが、「Disable」「Enable」では連動しない。
      • 2つ目を配置してから1つ目のテンプレートを取り除いた場合、3つ目以降の配置オブジェクトは連動しない。一旦すべての配置オブジェクトを取り除けば、武器以外は1つ目のnifと同様の動作になる。武器はこの場合配置しても連動しない。
    3. 1つ目が武器以外のテンプレート化されていないアイテムのとき
      • どのオブジェクトも、2つ目は連動しないし、テンプレート化することもできない。1つ目をテンプレート化するしかない。

目次に戻る


4. スクリプトでオブジェクトを配置する

4-1. 動かせるオブジェクトを出現させる

特殊な仕様を確認できたところで、スクリプトの書き方を見ていきましょう。

前回のおさらいになりますが、武器、その他の動かせるオブジェクトを配置するときは、「PlaceAtMe」で出現させて、「SetPosition」で移動する方法を取ります。

Potion Property potSweet Auto ; スイートロール

Actor actPlayer = Game.GetPlayer() ; プレイヤー
Float fltAngZ = actPlayer.GetAngleZ()
Float fltPosX = actPlayer.GetPositionX() + Math.Sin(fltAngZ) * 60
Float fltPosY = actPlayer.GetPositionY() + Math.Cos(fltAngZ) * 60
Float fltPosZ = actPlayer.GetPositionZ()

ObjectReference objPlace = actPlayer.PlaceAtMe(potSweet, 1, false, true)
objPlace.SetPosition(fltPosX, fltPosY, fltPosZ) ; 移動
objPlace.SetAngle(0, 0, fltAngZ) ; 回転
objPlace.Enable() ; 表示

「PlaceAtMe」の第1引数に、出現させたいベースオブジェクトを指定します。第4引数に「true」を指定することで、Disable の状態で出現させることができます。
初期位置はプレイヤーの足元(足の裏)ですので、「SetPosition」を使って少し前方に移動しています。「SetAngle」で角度も決めたら、「Enable」で初めて表示します。

4-2. スクリプトで出現させたものを位置連動させる

オブジェクトを出現させるとき、セル内にすでに1つ目が存在するかどうかを判定することは困難です。そのため、プレイヤーが1つ目をドロップしてテンプレート化していない場合、スクリプトで出現させたものも位置連動していない可能性があります。
その場合「SetPosition」での移動は有効ですが、セルのロードを挟まずにプレイヤーが移動させても座標情報は変わりません。

スクリプトだけではこの問題を解決できません。対策としては、次の3つが考えられます。

  • nifファイルをMOD専用フォルダに入れて使い、見た目は同じの別オブジェクトとして出現させる
  • 特定のセルに限定できるなら、あらかじめCKでセル内のどこかに配置し、espにテンプレート情報を持たせておく
  • セルのロードを挟むよう、ユーザーに注意喚起する

"Data\meshes" 内にMOD専用フォルダを作ってnifファイルを入れると、nif自体が同じファイル名でも、違うnifとして認識されるようです。
このnifを使うMOD専用オブジェクトを用意すれば、原則としてテンプレート化できるので対策の1つになります。
MOD専用オブジェクトには以下のようなスクリプトを付けておきます。

Event OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer)
    If akNewContainer == None ; ドロップしたとき
        Self.Disable()
        Utility.Wait(0.001)
        Self.Enable()
    EndIf
EndEvent

1つ目をドロップした瞬間に、それをテンプレート化しています。2つ目以降のドロップのときにはスクリプトの意味がありませんが、負荷はほとんどないので問題ありません。
こうすることで、ドロップした際でも「PlaceAtMe」で出現させた際でも位置連動するようなアイテムを作ることができます。

ただし、元々ドロップできないオブジェクトのnifを流用した場合は、2つ目以降が連動しない可能性がありますので、必ずテストプレイで確かめましょう。
2つ目が連動しないnifの場合、ユーザーへの注意喚起をしてセルのロードを挟んでもらうしかないかもしれません。

4-3. 動かせないオブジェクトを出現させる

家具や宝箱など、ぶつかっても動かせないオブジェクトを出現させる場合です。出現させるスクリプトはほぼ同様なのですが、注意点をいくつか挙げておきます。

まず、オブジェクトの向きを確認しておく必要があります。プレイヤーの向きと同じにしたときに正対するものもありますし、その逆もあります。これは実際に一度出現させてみて確認しましょう。
その上で、「SetAngle」でプレイヤーの向きである「fltAngZ」を指定するか、逆向きに「fltAngZ + 180」を指定するかを決めます。

向きを決めたら、プレイヤーとオブジェクトの距離をどれくらい離すか検討します。
動かせないオブジェクトというのは大きいものが多いので、向きによっても最適な距離がかなり変わります。これも実際に出現させて確認した方が良いでしょう。

位置情報の連動については、特に何も気にする必要はありません。何個目かに関わらず「PlaceAtMe」で出現させるだけです。
家具が属する「Furniture」や宝箱が属する「Container」タイプのオブジェクトというのはインベントリに所持できませんので、ドロップするときのことを考慮する必要はありません。また、プレイヤーがぶつかっても動かないため、移動させるには「SetPosition」を使うしかなく、見えている位置と座標は連動しています。テンプレートの概念も考えなくて大丈夫です。

目次に戻る


5. スクリプトでオブジェクトを固定する

5-1. 動かせるオブジェクトを固定する

出現させたオブジェクトをスクリプトで固定する方法も書いておきます。
上記 4-1. 動かせるオブジェクトを出現させる のスクリプトに、以下の行を追記します。

Utility.Wait(0.01)
objPlace.SetMotionType(4)

出現スクリプトの最後で「Enable」関数を使って表示しました。その後に「Wait」を入れてから、「SetMotionType」関数で動かないようにします。
「SetMotionType」の引数は、「4」を指定すると動かなくなり、「1」を指定すると動くようになります。

ここでのポイントは、固定する前に「Enable」で表示しておくことと、「Wait」を入れてから「SetMotionType」を書くことです。
オブジェクトが Disable の状態では固定できませんし、Wait を入れて完全に Enable になるのを待たないと、固定に失敗することがあります。

以上はゲームプレイ中にスクリプトを使って直接固定する方法ですが、もしCKのレンダーウィンドウへ配置したオブジェクトを固定したい場合は、CKでオブジェクトを固定する方法 を参考にしてください。

5-2. 動かせないオブジェクトを完全固定しないように気をつける

家具や宝箱等の動かせないオブジェクトに対しては、「SetMotionType(4)」を使わないように気をつけましょう。

もしこれを指定してしまうと、スクリプトを使っても一切移動できなくなります。
「SetMotionType(1)」を指定するまで「SetPosition」が効かなくなりますので、移動させるつもりだったのに動いていないということになりかねません。
「SetMotionType(4)」を使うのは、あくまでも動かせるオブジェクトを固定したいときです。

目次に戻る


6. スクリプトで既存オブジェクトを再配置する

6-1. 非表示にできるかどうか判定する

スクリプトでオブジェクトを再配置する方法は、espで配置された既存のものでも、スクリプト等で配置したものでも原則として変わりません。
ここでは、既存オブジェクトを再配置するときの話題を書いておきます。

まず、既存オブジェクトを「Disable」関数で非表示にしたいときです。
これができるオブジェクトかどうかは明確に判定できます。「Enable Parent」が設定されていないオブジェクトなら「Disable」関数が有効ですし、設定されているオブジェクトは「Disable」関数を使っても無視されます。
Enable Parent の設定を確認するスクリプトは以下のようになります。

ObjectReference objTarget ; 対象のオブジェクト

If objTarget.GetEnableParent() != None ; Enable Parent が設定されている
    Debug.MessageBox("Disableできません。")
Else ; 設定されていない
    objTarget.Disable()
    Debug.MessageBox("Disableしました。")
EndIf

「GetEnableParent」関数で、そのオブジェクトの Enable Parent を取得できます。それによって条件分岐しておくことで、可能な場合にのみ Disable を行うことができます。
ただし、「GetEnableParent」関数を使うにはSKSEが必要です。

6-2. 移動できない家具の存在

オブジェクトの位置情報に関連して、移動できない家具問題も最後に挙げておこうと思います。

家具の中には、「SetPosition」を使って移動させようとしても動かないものが存在します。こういった家具は、CKのレンダーウィンドウで位置を移動させたespを作成しても、すでに作られたセーブデータでは移動することができません。最初に出現した位置そのままです。

この移動できない家具は、espによって配置されている「Static」や「Furniture」のオブジェクトが該当し、その位置はニューゲーム時のesp内位置情報で固定されるようです。
具体的には収納のない棚、ベッド、椅子などが該当します。これらに対して「SetPosition」を行っても、完全に無視されます。ニューゲームをすれば、espにしたがって位置は変わります。

これに対し「Activator」、「Container」、「MovableStatic」等のオブジェクトは、「SetPosition」で動かすことができます。
また、スクリプトで新規配置したオブジェクトならセーブデータにしか位置情報がありませんので、「Furniture」のベッドであっても動かすことができます。

この動かせない家具については、今のところニューゲーム以外で動かす方法が見つかりません。もしMOD作成時にこういった家具を移動させたい場合は、CKで既存オブジェクトを非表示にする方法 を使って非表示にし、代わりとなる同じ家具を希望の位置に新しく配置すると良いでしょう。

目次に戻る


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

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

トップに戻る

0 件のコメント:

コメントを投稿

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