2018年9月27日

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

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

スカイリムにおける「オブジェクトの位置と角度」は、一筋縄ではいかない部分があります。
スクリプトでオブジェクトを配置するときの基本と特殊な仕様について、2回に分けて書いていきたいと思います。今回はまず基本編です。


目次

  1. オブジェクトの「位置」を取得したり変更したりする
  2. オブジェクトの「角度」を取得したり変更したりする
  3. プレイヤーを基点にして位置を指定する
  4. プレイヤーを基準にして向きを指定する

1. オブジェクトの「位置」を取得したり変更したりする

1-1. 位置の表し方

配置されたオブジェクトの位置は、そのセル内における「XYZ座標」によって表されます。セルの原点からX軸、Y軸、Z軸の3方向にどれだけ離れているか、ということです。
例として、ドラゴンズリーチを見てみましょう。

XY平面がいわゆる水平面になっています。多くの室内セルでは、入室した向きがY軸方向になります。
Z軸は高さを表します。セルの原点と建物との位置関係はセルによって異なりますので、床面のZ座標が0とは限りません。

1-2. 位置を取得する「GetPosition」

それでは、スクリプトからオブジェクトの座標を取得してみましょう。次の3つの関数を使います。

  • GetPositionX() … X座標
  • GetPositionY() … Y座標
  • GetPositionZ() … Z座標

例として、ある椅子のX座標を取得するときは以下のようなスクリプトになります。

ObjectReference Property objChair Auto ; 椅子のObjectReference

Float fltPosX = objChair.GetPositionX() ; X座標をFloat型に取得

「objChair」プロパティに、対象の椅子を指定してください。
座標値は小数を含むFloat型で表されます。これで「fltPosX」というFloat型変数に、X座標を取得することができました。

1-3. 位置を変更する「SetPosition」

位置を変更する関数は次のように書きます。

  • SetPosition(X座標, Y座標, Z座標)

X座標を変更して椅子を移動させたい場合は、前述のスクリプトに以下の行を追記します。

objChair.SetPosition(fltPosX + 100, objChair.GetPositionY(), objChair.GetPositionZ())

これで、X軸方向に「100」移動します。
「SetPosition」関数の3つの引数(X/Y/Z座標)は省略できませんので、変更しない箇所は「GetPosition」関数を使って補完します。

座標値には単位がありません。大体の目安としては、一般的な大人NPCで床面から目線の高さまでが「100強」です。

目次に戻る


2. オブジェクトの「角度」を取得したり変更したりする

2-1. 角度の表し方

オブジェクトの角度は非常に表現しづらいです。簡単に言えば「XYZ軸に対する角度」なんですが、厳密に言うと正しくありません。

通常、XYZ軸といえばセルの軸を指します。一方、オブジェクトを形作っているnifファイルにもXYZ軸があり、オブジェクトの角度指定がすべて0のときは、セルのXYZ軸とnifのXYZ軸の方向は同じです。オブジェクトの角度を変化させると、nifの軸はオブジェクトと連動して回転し、セルの軸とずれます。

以上を元に、オブジェクトの角度指定を言葉で表現すると以下のようになります。

  • セルのXYZ軸で見たとき、Z→Y→Xの順で回転させた角度
  • nifのXYZ軸で見たとき、X→Y→Zの順で回転させた角度

…と文章で書いても分かりづらいですよね。次の 2-2 で画像が出てきますので、それを参考にしてください。

角度の正負は、以下のように表現できます。同じことですが、2通りで表現しておきます。

  • オブジェクトから軸矢印の先端を見たとき、反時計周りが正。
  • 軸矢印の先端からオブジェクトを見たとき、時計回りが正。

また、「角度0」を指定したときの向きは覚えておくと便利です。nifにもよりますが、ほとんどのオブジェクトで以下のようになります。

  • XY角度がともに0 … 水平に配置される。
  • すべての角度が0 … 水平かつ、セルのY軸方向(12時の方向)に向けて配置される。
2-2. 角度を画像で見てみる

「リュート」を例にして、90°ごとに画像を撮ってみました。3色の矢印はセルのXYZ軸です。
角度を (X, Y, Z) の形式で併記してあります。

(0, 0, 0)
(0, 0, 90)
(0, 0, 180)
(0, 0, 270)
(90, 0, 0)
(90, 0, 90)
(90, 0, 180)
(90, 0, 270)
(180, 0, 0)
(180, 0, 90)
(180, 0, 180)
(180, 0, 270)
(270, 0, 0)
(270, 0, 90)
(270, 0, 180)
(270, 0, 270)
(0, 90, 0)
(0, 90, 90)
(0, 90, 180)
(0, 90, 270)
(0, 270, 0)
(0, 270, 90)
(0, 270, 180)
(0, 270, 270)

実は、同じ見え方でも異なる角度で表現できるものもあります。
例えば (0, 270, 270) は、(270, 270, 0) でも同じ向きになります。

2-3. 角度を取得する「GetAngle」

スクリプトからオブジェクトの角度を取得するには、次の3つの関数を使います。

  • GetAngleX() … X角度
  • GetAngleY() … Y角度
  • GetAngleZ() … Z角度

ある椅子のZ角度を取得するときは、以下のようなスクリプトになります。

ObjectReference Property objChair Auto ; 椅子のObjectReference

Float fltAngZ = objChair.GetAngleZ() ; Z角度をFloat型に取得

座標値と同様に、角度も小数を含むFloat型で表されます。

2-4. 角度を変更する「SetAngle」

角度を変更する関数は次のように書きます。

  • SetAngle(X角度, Y角度, Z角度)

Z角度を変更して椅子の向きを変える場合は、前述のスクリプトに以下の行を追記します。

objChair.SetAngle(objChair.GetAngleX(), objChair.GetAngleY(), objChair.GetAngleZ() + 180)

これで「180°」向きを変えます。
角度の単位は「° (度)」です。マイナス値、および360°以上の値でも指定できます。

目次に戻る


3. プレイヤーを基点にして位置を指定する

3-1. プレイヤーの正面に置く

ここからはプレイヤーを基点にして考えていきます。まずは基本形として、正面にオブジェクトを置いてみましょう。
以下のようなスクリプトで、「fltDistance」に距離を指定します。

ObjectReference Property objTarget Auto ; 移動するオブジェクト
Float fltDistance = 100 ; プレイヤーからの距離を指定

Actor actPlayer = Game.GetPlayer() ; プレイヤー
Float fltAngZ = actPlayer.GetAngleZ() ; プレイヤーのZ角度

; オブジェクトのXYZ座標を計算する
Float fltPosX = actPlayer.GetPositionX() + Math.Sin(fltAngZ) * fltDistance
Float fltPosY = actPlayer.GetPositionY() + Math.Cos(fltAngZ) * fltDistance
Float fltPosZ = actPlayer.GetPositionZ()

objTarget.SetPosition(fltPosX, fltPosY, fltPosZ) ; オブジェクトを移動

オブジェクトのXY座標は、プレイヤーの位置を基準とし、距離をXY方向に分解して加算します。

三角関数の部分は下図のような関係性にあります。

点Pと点Aの距離をaとし、角度をθとすると、距離のX成分は「aSin(θ)」、Y成分は「aCos(θ)」です。
角度が12時方向を0として時計回りに正であることに注意してください。これは、Z軸矢印の先端からXY平面を見下ろしているからです。
点Pはプレイヤーを模しており、プレイヤーのXY角度は常に0ですので、プレイヤーのZ角度をそのままθとして考えることができます。

以上により、プレイヤーの位置を基準とし、指定された距離をプレイヤーのZ角度を用いてXY方向に分解、加算することで、プレイヤーの正面にオブジェクトを配置することができます。

3-2. プレイヤーの斜め後ろや斜め前に置く

正面に置く方法を応用して、オブジェクトを好きな場所に置いてみましょう。
まずは単純に、プレイヤーのZ角度が0で、Y軸方向を向いている状態を考えてみます。

上図で点Oからみたとき、点Aは右斜め後ろの位置にあり、角度は135°です。点Bは左斜め前にあり、角度は300°です。
これを踏まえて、プレイヤーのZ角度(θ)を考慮すると下図のようになります。

点P(プレイヤー)からみたとき、点Aは「θ+135°」の位置にあります。点Bは「θ+300°」です。これを先ほどの正面に置くスクリプトへそのまま応用することができます。
プレイヤーの右斜め後ろへオブジェクトを置きたいときは、オブジェクトのXY座標を以下のように計算します。

Float fltPosX = actPlayer.GetPositionX() + Math.Sin(fltAngZ + 135) * fltDistance
Float fltPosY = actPlayer.GetPositionY() + Math.Cos(fltAngZ + 135) * fltDistance

簡単ですね!
左斜め前に置きたければ300を足せば良いですし、真後ろに置く場合は180を足します。置く方向と距離さえ指定しておけば、プレイヤーの向きに応じて計算してくれます。

3-3. オブジェクトがどの方向にあるかを取得する

左斜め前は300を足す、と言いましたが、逆にこの角度を取得することもできます。
プレイヤーから見て、オブジェクトがどの角度にあるかを取得するスクリプトは以下のようになります。

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

Actor actPlayer = Game.GetPlayer() ; プレイヤー

Float fltDirection = actPlayer.GetHeadingAngle(objTarget)

これで、「fltDirection」に角度を取得することができます。正面にあれば「0」が返りますし、真後ろなら「180」が返ります。

目次に戻る


4. プレイヤーを基準にして向きを指定する

4-1. プレイヤーの向きに合わせて置く

オブジェクトのZ角度をプレイヤーを基準にして指定してみましょう。プレイヤーの向きを「GetAngleZ」で取得して利用します。

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

Actor actPlayer = Game.GetPlayer() ; プレイヤー
Float fltAngZ = actPlayer.GetAngleZ() ; プレイヤーのZ角度

objTarget.SetAngle(0, 0, fltAngZ + 180) ; プレイヤーと正対する向きに置く

nifにもよりますが、家具などの水平で良いものはXY角度に0を指定します。「fltAngZ」に任意の値を足せば、その分だけ回転して置くことができます。
オブジェクトの正面がどちらを向いているかはnifによって異なりますので、実際に確かめて指定するしかありません。

4-2. プレイヤーの向きを元に、XY軸と平行に置く

家具を配置するなど、オブジェクトを壁と平行に置きたいときがあります。単純にプレイヤーの向きを基準にすると、平行に置くのは結構難しいです。
そこで、プレイヤーの向きを90°刻みに変換し、オブジェクトを綺麗に配置してみましょう。

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

Actor actPlayer = Game.GetPlayer() ; プレイヤー

Int intAngZ = Math.Ceiling(actPlayer.GetAngleZ()) ; プレイヤーのZ角度の整数値
Float fltAngZ = (intAngZ / 90 + (intAngZ % 90) / 45) * 90 ; 90°刻みに変換

objTarget.SetAngle(0, 0, fltAngZ)

まずは「intAngZ」に、プレイヤーのZ角度を整数値で取得します。「Ceiling」は小数以下を切り上げて整数にする関数です。
計算式の「/」は商を求め、「%」は余りを求めます。この計算は整数同士でないとできませんので、先に intAngZ を取得したわけです。
intAngZ を90°で割った商と、余りの部分を45°で割った商を足して、90を掛けています。これで90°刻みの数値が取得できます。

スクリプトに四捨五入のような関数があれば良いのですが、今のところ見当たらないので上記の方法を取っています。

基本編は以上です。オブジェクトを任意の位置に、任意の角度で配置する方法を書いてみました。
次回は検証編と題して、その特殊な仕様について触れていきたいと思います。

目次に戻る


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

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

トップに戻る

0 件のコメント:

コメントを投稿

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