【ベータ機能】プレイヤーを吹き飛ばす(プレイヤー操作)

本記事はCluster Creator Kitの「ベータ機能」を利用した解説になります。
ベータ機能では正式リリース前の機能を使うことができます。正式リリース前のため、不安定な挙動をしたり将来的に挙動が変わる可能性があります。

利用にはCluster Creator Kitでベータ機能を設定した状態でアップロードを行う必要があります。ワールドクラフトで利用する場合は、ワールドクラフトのワールドを新規作成する際にベータ機能の設定を行う必要があります。設定方法はこちらをご覧ください。

今回は「プレイヤーに干渉してアバターの位置などを操作したり、速度を加えて強制的に動かしたりする」機能を使ったアイテムの作成方法を紹介します。
この機能を使って、プレイヤーをジャンプさせたり弾き飛ばしたりといったことができるアイテムをつくります。

上に乗ると自動で打ち上げられるジャンプ台をつくってみます。

空のGameObjectにScriptable Itemコンポーネントを追加し、以下のスクリプトを設定します。
子オブジェクトに3Dモデルを配置するほか、「Overlap Detector Shape」コンポーネントを設定した侵入判定用のコライダーを作成しておきます。

const velocity = new Vector3(0, 10, 0);


$.onUpdate(deltaTime => {
    // 初期化
    if (!$.state.initialized) {
        $.state.initialized = true;
        $.state.overlapPlayers = [];
    }


    // 前のフレームで接触していたプレイヤーIDの一覧
    let previousOverlapPlayers = $.state.overlapPlayers;


    // このフレームで接触しているプレイヤーIDの一覧
    let currentOverlapPlayers = [];


    // 接触しているオブジェクトをすべて取得
    let overlaps = $.getOverlaps();


    overlaps.forEach(overlap => {
        // 接触しているオブジェクトがプレイヤーであるかどうかを確認
        let playerHandle = overlap.object.playerHandle;
        if (playerHandle == null) return;


        // 現在接触しているプレイヤーの一覧に追加
        currentOverlapPlayers.push(playerHandle.id);


        // 前のフレームで接触していたプレイヤーは除外
        // playerHandle.addVelocityの実行には頻度制限があるためその対策、また接触し続けた場合に加速し続けてしまうことを防止
        if (previousOverlapPlayers.includes(playerHandle.id)) return;


        // プレイヤーに一定の速度を加えて打ち上げる
        playerHandle.addVelocity(velocity);
    });


    $.state.overlapPlayers = currentOverlapPlayers;
});

新しい機能を利用している部分を中心に、サンプルコードを解説します。

let playerHandle = overlap.object.playerHandle;
if (playerHandle == null) return;

このoverlap$.getOverlaps()で取得した配列の要素で、アイテムと重なっているオブジェクトを示します。重なっているオブジェクトがプレイヤーであった場合、overlap.object.playerHandleでそのプレイヤーのハンドルを取得できます。対象がプレイヤーでない場合はnullになります。

playerHandle.addVelocity(velocity);

playerHandle.addVelocity()は対象のプレイヤーを特定の方向・速度で加速します。ここではY軸方向に一定の速度を加えることで、上に向かって打ち上げています。

攻撃が当たると弾き飛ばされる武器をつくってみます。

空のGameObjectにGrabbable ItemScriptable Itemの各コンポーネントを追加し、Scriptable Itemに以下のスクリプトを設定します。
こちらも3Dモデルや物理判定用のコライダーに加えて、Overlap Detector Shapeを設定した当たり判定用のコライダーを作成しておきます。

const speed = 5;


$.onGrab((isGrab, isLeft, player) => {
    // このアイテムを持っているプレイヤーを保存
    if (isGrab) $.state.grabbingPlayer = player;
    else $.state.grabbingPlayer = null;


    $.state.overlapPlayers = [];
});


$.onUpdate(deltaTime => {
    // 初期化
    if (!$.state.initialized) {
        $.state.initialized = true;
        $.state.overlapPlayers = [];
        $.state.grabbingPlayer = null;
    }


    // このアイテムを持っているプレイヤーを取得し、有効かどうかチェック
    let grabbingPlayer = $.state.grabbingPlayer;
    if (grabbingPlayer == null || !grabbingPlayer.exists()) return;


    // アイテムを持っているプレイヤーの位置を取得
    let grabbingPlayerPosition = grabbingPlayer.getPosition();


    // 前のフレームで接触していたプレイヤーIDの一覧
    let previousOverlapPlayers = $.state.overlapPlayers;


    // このフレームで接触しているプレイヤーIDの一覧
    let currentOverlapPlayers = [];


    // 接触しているオブジェクトをすべて取得
    let overlaps = $.getOverlaps();


    overlaps.forEach(overlap => {
        // 接触しているオブジェクトがプレイヤーであるかどうかを確認
        let playerHandle = overlap.object.playerHandle;
        if (playerHandle == null) return;


        // アイテムを持っているプレイヤー自身は除外
        if (playerHandle.id == grabbingPlayer.id) return;


        // 現在接触しているプレイヤーの一覧に追加
        currentOverlapPlayers.push(playerHandle.id);


        // 前のフレームで接触していたプレイヤーは除外
        // playerHandle.addVelocityの実行には頻度制限があるためその対策、また接触し続けた場合に加速し続けてしまうことを防止
        if (previousOverlapPlayers.includes(playerHandle.id)) return;


        // アイテムが当たったプレイヤーを、アイテムを持っているプレイヤーから遠ざかる方向に弾き飛ばす
        let direction = playerHandle.getPosition().clone().sub(grabbingPlayerPosition).normalize();
        playerHandle.addVelocity(direction.clone().multiplyScalar(speed));
    });


    // 接触しているプレイヤーの一覧を更新
    $.state.overlapPlayers = currentOverlapPlayers;
});
$.onGrab((isGrab, isLeft, player) => {
    // このアイテムを持っているプレイヤーを保存
    if (isGrab) $.state.grabbingPlayer = player;
    else $.state.grabbingPlayer = null;

$.onGrab()に引数が追加され、「左右どちらの手で持っているか」「持っているプレイヤーのハンドル」を受け取れるようになりました。

※今まで通りisGrabのみを引数に取ることもできます。$.stateにPlayerHandleを代入することもできるので、アイテムを持っているプレイヤーの情報を保存しておくことができます。

if (playerHandle.id == grabbingPlayer.id) return;
currentOverlapPlayers.push(playerHandle.id);

playerHandle.idでそのプレイヤーを一意に識別するIDが取得できます。
接触しているプレイヤーがアイテムを持っているプレイヤー自身と一致するかどうかを判定したり、接触しているプレイヤーのIDを$.state.overlapPlayersに保存して接触し続けているかどうかの判定に利用したりしています。