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

空の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 handle = overlap.handle;
if (handle == null || handle.type !== "player") return;
// 現在接触しているプレイヤーの一覧に追加
currentOverlapPlayers.push(handle.id);
// 前のフレームで接触していたプレイヤーは除外
// playerHandle.addVelocityの実行には頻度制限があるためその対策、また接触し続けた場合に加速し続けてしまうことを防止
if (previousOverlapPlayers.includes(handle.id)) return;
// プレイヤーに一定の速度を加えて打ち上げる
handle.addVelocity(velocity);
});
$.state.overlapPlayers = currentOverlapPlayers;
});サンプルコードの解説
新しい機能を利用している部分を中心に、サンプルコードを解説します。
let handle = overlap.handle;
if (handle == null || handle.type !== "player") return;このoverlapは$.getOverlaps()で取得した配列の要素で、アイテムと重なっているオブジェクトを示します。overlap.handleで重なっているオブジェクトのハンドルを取得できます。ハンドルはPlayerHandleまたはItemHandleのいずれかで、handle.typeが"player"であればプレイヤー、"item"であればアイテムです。対象がプレイヤーでもアイテムでもない場合(地面など)はnullになります。
handle.addVelocity(velocity);PlayerHandle.addVelocity()は対象のプレイヤーを特定の方向・速度で加速します。ここではY軸方向に一定の速度を加えることで、上に向かって打ち上げています。
「ノックバック」サンプルコード
攻撃が当たると弾き飛ばされる武器をつくってみます。

空のGameObjectにGrabbable ItemとScriptable 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 handle = overlap.handle;
if (handle == null || handle.type !== "player") return;
// アイテムを持っているプレイヤー自身は除外
if (handle.id == grabbingPlayer.id) return;
// 現在接触しているプレイヤーの一覧に追加
currentOverlapPlayers.push(handle.id);
// 前のフレームで接触していたプレイヤーは除外
// playerHandle.addVelocityの実行には頻度制限があるためその対策、また接触し続けた場合に加速し続けてしまうことを防止
if (previousOverlapPlayers.includes(handle.id)) return;
// アイテムが当たったプレイヤーを、アイテムを持っているプレイヤーから遠ざかる方向に弾き飛ばす
let direction = handle.getPosition().clone().sub(grabbingPlayerPosition).normalize();
handle.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 (handle.id == grabbingPlayer.id) return;currentOverlapPlayers.push(handle.id);playerHandle.idでそのプレイヤーを一意に識別するIDが取得できます。
接触しているプレイヤーがアイテムを持っているプレイヤー自身と一致するかどうかを判定したり、接触しているプレイヤーのIDを$.state.overlapPlayersに保存して接触し続けているかどうかの判定に利用したりしています。




















