Constraintで別のItemに6つの数値を同時に送る
こんにちは!かおもです!!
Parent ConstraintとClusterScriptを使って、別のItemに数値を送る方法があります。
ここでは、回転角を含め合計6つの数値を同時に送る方法を紹介します。
0.実際に動きを確認できるサンプルあります
BOOTHにスクリプトとItemを含めたPrefabを公開しています。
CCK 2.2.0
1.基本的な仕組み
ClusterScriptでは、Itemの子オブジェクトの位置を指定取得できます。
UnityのParent Constraintでは、指定されたオブジェクトは、元オブジェクトと同じ動きをします。
これを応用して値を送ります。
以下のように、ItemAとItemBの子GameObject(子A、子B)がConstraintで繋がっているとします。
この時、子Aを動かすと、子Bも同じだけ動きます。
ClusterScriptでは、子Aを動かす量を指定できます。
同様に子Bが動いた量も取得できます。
つまり、ItemAで子Aを1動かすと、子Bが1動き、ItemBで子Bが動いた1を取得できます。
このようにして、ItemからItemへ数値を送ることができる、というのが基本的な仕組みです。
2.Unityですること
基本的には、子オブジェクトをParent Constraintで繋げるのみです。
送り側か受け側のどちらで繋げてもいいと思います(多分)。
送り側
受け側
注意点があります。
- Parent Constraintで繋がっている送り側と受け側の子オブジェクトは、グローバル座標上で同じ位置になるようにしてください。
- 送り側と受け側の子オブジェクトの位置と回転は0になるようにしてください。
1.のほうは、ズレていると回転した時に位置も変わってしまうためです。
2.のほうは、0でないと値がその分ズレてしまうためです。
画像のようになっていればOKです。
senderオフセットとreceiverオフセットは、位置調整のために使っています。
3.ClusterScriptですること
送り側はSubNodeに座標を指定し、受け側はSubNodeの座標を受け取ります。
使っているスクリプト全文です。
const SUBNODE_SENDER = "sender";
const SUBNODE_RECEIVER = "receiver";
const ITEM_VALUE_PREFIX = "value";
const QUATERNION_MARGIN = 1000;
const sender = $.subNode(SUBNODE_SENDER);
const receiver = $.subNode(SUBNODE_RECEIVER);
const sendValues = (values) => {
const v0 = values[0] ?? 0;
const v1 = values[1] ?? 0;
const v2 = values[2] ?? 0;
const v3 = (values[3] ?? 0) / QUATERNION_MARGIN;
const v4 = (values[4] ?? 0) / QUATERNION_MARGIN;
const v5 = (values[5] ?? 0) / QUATERNION_MARGIN;
//ノルムが1になるようにする
const w = Math.sqrt(1 - (v3 * v3 + v4 * v4 + v5 * v5));
const p = new Vector3(v0, v1, v2);
const r = new Quaternion(v3, v4, v5, w);
$.log(p);
$.log(r);
sender.setPosition(p);
sender.setRotation(r);
};
const receiveValues = () => {
const ret = [];
const p = receiver.getPosition();
const r = receiver.getRotation();
$.log(p);
$.log(r);
ret.push(Math.round(p.x));
ret.push(Math.round(p.y));
ret.push(Math.round(p.z));
ret.push(Math.round(r.x * QUATERNION_MARGIN));
ret.push(Math.round(r.y * QUATERNION_MARGIN));
ret.push(Math.round(r.z * QUATERNION_MARGIN));
return ret;
}
$.onInteract(() => {
if(sender.getEnabled()){
const values = [0, 1, 2, 3, 4, 5].map(i =>
$.getStateCompat("this", ITEM_VALUE_PREFIX + i, "integer")
);
sendValues(values);
}
if(receiver.getEnabled()){
const values = receiveValues();
[0, 1, 2, 3, 4, 5].forEach(i =>
$.setStateCompat("this", ITEM_VALUE_PREFIX + i, values[i])
);
}
});
(面倒だったので)送り側受け側で同じスクリプトを使っています。
重要なのはsendValues
とreceiveValues
の中身です。
順に確認していきます。
3.1 送り側(sendValues)の処理
v0からv2は位置の値をそのまま数値として送れるので、特に難しいことはしていません。
v3からv5は回転の値を数値として送るのですが、クォータニオンの制限のために、少し工夫をしています。
クォータニオンにはノルム(長さみたいなもの)が1であるという制限があります。
このため、v0からv2のように、そのまま数値を指定しようとすると、ノルムが容易に1を超えてしまいます。
そのため、まず、以下のように扱う数値が十分に1以下になるような値で割って、小さくします。
ここでは、おおよそ100前後の値を扱えるように、1000(QUATERNION_MARGINと定義)で割っています。
const v3 = (values[3] ?? 0) / QUATERNION_MARGIN;
const v4 = (values[4] ?? 0) / QUATERNION_MARGIN;
const v5 = (values[5] ?? 0) / QUATERNION_MARGIN;
次に、ノルムが1になるような調整を行います。
クォータニオンには4つ目のパラメータがあるので、その値を調整しノルムが1になるようにします。
以下のように計算した値を指定します。三平方の定理の4次元版です。
const w = Math.sqrt(1 - (v3 * v3 + v4 * v4 + v5 * v5));
これにより、クォータニオンに値を設定することができます。
const r = new Quaternion(v3, v4, v5, w);
sender.setRotation(r);
なお、この調整を行わなかった場合、内部でv3からv5の値は自動で調整され、値が変化してしまいます。
3.2 受け側(receiveValues)の処理
受け側は簡単です。
pの値は、そのまま値として受け取れます。
rの値は、送り側でQUATERNION_MARGIN
で割られているので、QUATERNION_MARGIN
を掛けることで復元すればOKです。
ret.push(Math.round(r.x * QUATERNION_MARGIN));
ret.push(Math.round(r.y * QUATERNION_MARGIN));
ret.push(Math.round(r.z * QUATERNION_MARGIN));
Math.roundで四捨五入しているのは、わずかに発生する計算誤差を吸収するためです。
なので、小数の値をやり取りしたい場合は、10倍して送るなどの工夫が必要でしょう。
注意点があります。
ClusterScriptの仕様上、座標を変更しても、即座に指定した値の位置にならない場合があります。
基本はふわっと動きます。
そのため、送り側が値を送っても、少し待ってから受け取る必要があります。
シムクラスターでは最低0.5秒待っています。
4.以上です!
良いClusterScriptライフを!!!
Discussion