2024年10月05日 - メタバース

clusterのスクリプトでドアを制作

clusterのスクリプト入門(2)」までで、スクリプトの基本的な書き方についてまとめました。今回は、clusterのスクリプトを使用してドアを制作します。

clusterのスクリプトでドアを制作

ドアの3DCGモデルを制作

Blenderでドアの3DCGモデルを制作し、FBXに出力します。ドアはスクリプトで開閉させるので、原点をドアの側面に設定します。FBXに出力する方法は、下記を参考にしてください。

制作環境構築

clusterのワールド制作環境を構築します。詳細は下記ページの「clusterでスクリプトを実行するための基本手順」を参考にしてください。

ドアのFBXをUnityで読み込み

Unityで空のゲームオブジェクトを作成し、名前を「door」にします。ドアのFBXをUnityで読み込み、「door」の子オブジェクトとして追加します。

「door」のインスペクタの「コンポーネントを追加」をクリックして、「Scriptable Item」を追加し、「Source Code Asset」に「door.js」を設定します。

ドアのFBXに、「Mesh Collider」コンポーネントを設定します。

スクリプトの記述

door.jsにスクリプトを記述します。

定数の宣言と初期設定

トップレベルで必要な定数を宣言し、ドアの本体部分である子オブジェクトを取得します。また、$.onStartで初期設定を行います。ドアの開閉アニメーションは$.onUpdateで制御しますが、アニメーションが何もせずに始まらないようにするために$.state.initializedをfalseに設定します。


//ドアを閉めたときの角度
const minAngle = 0;
//ドアを開いたときの角度
const maxAngle = 80;
//ドアを開く時間(秒)
const openTime = 0.8;
const doorBody = $.subNode('doorBody');

$.onStart(() => {
    //初期化状態フラグ
    $.state.initialized = false;
    //ドアの開閉状態フラグ
    $.state.isOpen = false;
    //タイマー
    $.state.time = 0;
});

$.onInteract(() => {
});

$.onUpdate(deltaTime => {
});

線形補完関数

線形補完関数は、2つの値の間を滑らかに補完するために使います。例えば、ドアが閉じた角度(minAngle)から開いた角度(maxAngle)へ移動するさい、途中の角度を計算するのに使います。lerp関数は、指定された割合(t)に応じて、開始点(a)から終了点(b)への中間値を計算します。


//線形補間関数
function lerp(a, b, t){
    if(b == a) {
        return a;
    }
    t = Math.min(Math.max(t, 0), 1); 
    return a + t * (b - a);
}

イージング関数

イージング関数は、アニメーションの動きをより自然に見せるために使います。線形補完関数は一定の速度で値を補完しますが、イージング関数を使うと、開始時はゆっくりで徐々に加速するような動きになります。


//イージング関数
function easeInQuart(t){
    return t * t * t * t;
}

オブジェクトをクリックしたときの処理

ドアをクリックして開閉状態を切り替えます。最初のクリック時にドアが開くように、$.state.initializedをtrueに設定します。また、連続したクリックによる誤動作を防ぐため、前回のクリックから2秒以上経過しているかを確認します。


$.onInteract(() => {
    //2秒以上経過している場合
    if($.state.time > 2){
        //初期化済み
        $.state.initialized = true; 

        //ドアの開閉状態を切り替える
        if($.state.isOpen == true){
            $.state.isOpen = false;
        }else{
            $.state.isOpen = true;
        }
        //タイマーをリセット
        $.state.time = 0;
    }
});

ドアの開閉アニメーション

$.onUpdateでドアの開閉アニメーションを制御します。変数angleにminAngleを代入して、ドアの現在の角度を初期化します。角度の初期化はトップレベルで行います。これにより、毎フレーム呼び出されるたびに角度がリセットされるのを防ぎます。

$.onUpdate内では、まずdeltaTimeで経過時間を取得し、これを$.state.timeに加算します。次に、$.state.initializedがtrueだったら、ドアの開閉状態をチェックします。ドアが開いている場合は、線形補完関数とイージング関数を使用して、ドアの角度をminAngleからmaxAngleの範囲で計算します。ドアが閉じている場合も同様に、ドアの角度をminAngleからmaxAngleの範囲で計算します。
次に、Quaternionオブジェクトを作成し、Vector3(0, angle, 0)を使用してY軸回りの回転を設定します。最後に、この回転をドアの本体に適用して、実際にドアをアニメーションさせます。


//ドアの現在の角度を初期化
let angle = minAngle;

$.onUpdate(deltaTime => {
    //経過時間を加算
    $.state.time += deltaTime;

    //初期化済みの場合
    if($.state.initialized == true){
        //ドアが開いている場合 
        if($.state.isOpen == true){
            angle = lerp(minAngle, maxAngle, easeInQuart($.state.time / openTime));
        //ドアが閉じている場合
        }else{
            angle = lerp(maxAngle, minAngle, easeInQuart($.state.time / openTime));
        }
        //ドアの回転を更新
        const rot = new Quaternion().setFromEulerAngles(new Vector3(0, angle, 0));
        doorBody.setRotation(rot);
    }
});

door.js

完成したdoor.jsになります。ドアをクリックもしくはタップすると開閉します。

clusterのスクリプトはUnity上では動作しないため、スクリプトの動作確認は、ワールドをアップロードして行います。


//ドアを閉めたときの角度
const minAngle = 0;
//ドアを開いたときの角度
const maxAngle = 80;
//ドアを開く時間(秒)
const openTime = 0.8;
//ドアの本体部分を取得
const doorBody = $.subNode('doorBody');

//線形補間関数
function lerp(a, b, t){
    if(b == a) {
        return a;
    }
    //tの範囲を0から1に制限
    t = Math.min(Math.max(t, 0), 1); 
    return a + t * (b - a);
}

//イージング関数
function easeInQuart(t){
    return t * t * t * t;
}

//初期設定
$.onStart(() => {
    //初期化状態フラグ
    $.state.initialized = false;
    //ドアの開閉状態フラグ
    $.state.isOpen = false;
    //タイマー
    $.state.time = 0;
});

//オブジェクトをクリックしたときの処理
$.onInteract(() => {
    //2秒以上経過している場合
    if($.state.time > 2){
        //初期化済み
        $.state.initialized = true; 

        //ドアの開閉状態を切り替える
        if($.state.isOpen == true){
            $.state.isOpen = false;
        }else{
            $.state.isOpen = true;
        }
        //タイマーをリセット
        $.state.time = 0;
    }
});

//ドアの現在の角度を初期化
let angle = minAngle;

//毎フレームの更新処理
$.onUpdate(deltaTime => {
    //経過時間を加算
    $.state.time += deltaTime;

    //初期化済みの場合
    if($.state.initialized == true){
        //ドアが開いている場合 
        if($.state.isOpen == true){
            angle = lerp(minAngle, maxAngle, easeInQuart($.state.time / openTime));
        //ドアが閉じている場合
        }else{
            angle = lerp(maxAngle, minAngle, easeInQuart($.state.time / openTime));
        }
        //ドアの回転を更新
        const rot = new Quaternion().setFromEulerAngles(new Vector3(0, angle, 0));
        doorBody.setRotation(rot);
    }
});