🙌

Constraintで別のItemに6つの数値を同時に送る

2023/01/18に公開約4,600字

こんにちは!かおもです!!

Parent ConstraintとClusterScriptを使って、別のItemに数値を送る方法があります。
ここでは、回転角を含め合計6つの数値を同時に送る方法を紹介します。

0.実際に動きを確認できるサンプルあります

BOOTHにスクリプトとItemを含めたPrefabを公開しています。
CCK 2.2.0
https://vkao.booth.pm/items/4485143

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で繋げるのみです。
送り側か受け側のどちらで繋げてもいいと思います(多分)。

送り側


受け側

注意点があります。

  1. Parent Constraintで繋がっている送り側と受け側の子オブジェクトは、グローバル座標上で同じ位置になるようにしてください。
  2. 送り側と受け側の子オブジェクトの位置と回転は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])
        );
    }
});

(面倒だったので)送り側受け側で同じスクリプトを使っています。

重要なのはsendValuesreceiveValuesの中身です。
順に確認していきます。

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

ログインするとコメントできます