🏪

clusterで複数人プレイのゲームが格段に作りやすくなった!

2024/12/17に公開

この記事は「Cluster #1 Advent Calendar 2024」の17日目の記事です。

2020年にclusterでゲームを作れるようになってから(特にその年の後半あたりから「ロジック」とかでフクザツなゲームもいけるようになってから)、ずーっと言われてきたのが「Ownerを取れるようにしてほしい!」ってことです。

Owner問題の基本

Ownerってのは何か? 公式の説明ではこんな感じですが……(ムズかしめなんでリンク先読まなくてもいいです)

clusterは複数人でプレイできるゲームなんで、

  • 敵とか弾とかボールの動きを
  • ワールド内にいる誰か(Owner) のPCやスマホで計算して
  • その結果を他のユーザーに送信
    って感じで管理しているんですね。

逆に言うと、Ownerじゃないプレイヤーは、計算結果が送られてくるまで待たないといけないのでボールや敵の動きがカクカクして見えます。

どういうときに問題になる?

乗り物なんかは乗るときに必ずクリック(タップ)するんで問題ありません。でもサッカーワールドとかだとどうでしょう? 他のプレイヤーからのパスを受けるとき、わざわざボールをクリックするんじゃゲームになりませんよね。

かと言ってOwnerが他の人のままだと、ボールがカクカクして非常に体験が悪いです。なので、2人以上でプレイするサッカーはかなりムリがあり、私も某コンテストでは1人プレイでシュート特化のワールドにしたりしました。

また、ワールド内を動いていると敵やNPCが出現する……なんていうワールドでも問題が起きます。
最初に入ったプレイヤーはいいですが、2人目はどうしても敵やNPCがカクカクして見えてしまう。プレイヤー2人が近くで戦っているなら「片方がガマンしてね」で済むかもしれませんが、遠く離れて戦っているなら「一番近くにいるプレイヤーがOwnerになればいいのに」 ってなりますよね。

解決策が!

4年間解決策がなかったこの問題、ついにclusterのベータ機能で解決しました。

その名もズバリ、$.requestOwner
「このプレイヤーをOwnerにしたい!」ってアイテムのほうからリクエストできるんです。


ワールド内に複数プレイヤーがいたら、近くにいるプレイヤーを一定時間ごとに「Owner」に変更するワールド

一定時間ごとに周囲にプレイヤーがいないかチェックして、プレイヤーがいたらそのプレイヤーをOwnerにする。2人以上いたら、一番距離が近い人をOwnerにする。そんなことがついに可能になりました!

https://cluster.mu/w/c024f29b-33cc-4e29-83dd-c33a7a4ed377
こちらは習作のワールドです。1人でプレイしてもよくわからないと思いますが、2人でプレイしている時に違いが出ます。敵がなめらかに動いて見えるのは「その敵に一番近いプレイヤー」で、「最初に入ったプレイヤー」ではありません!

サッカーのパスとかなら、パス先の相手を大体決めておいて、蹴ったときにOwnerを変更……とかすればスムーズにつながります!

Owner変更だけするスクリプトをつけるのもアリ

普段スクリプトを使わない人も、この後に書く「Ownerを近くのプレイヤーに一定時間ごとに変更する」スクリプトをつけてしまうのは手です。スクリプトを使わないアイテムであっても、ScriptableItemにして、このスクリプトを貼りつけてしまえば「Owner変更」の機能だけが加わります!


作例ワールド。記事の一番下で紹介

従来からある「トリガー」「ギミック」などでアイテムを動かしている人も、一度試しにつけてみて、ワールドに複数人で入ってみてください! 特に離れた場所でプレイしているときに違いが見えてくると思いますよ。

注意点

  • 乗り物とか、持って使うアイテムなら元々Owner変更はスムーズなのでわざわざつけなくても大丈夫です。
  • みんなで一緒に見て回っている場合は、結局誰かがOwnerにならないといけないので、そこまでプレイ体験は変わりません。
  • ギミックとかではなくAnimatorでモノを動かしている場合は、おそらく特に変わりません。
  • 当然ながら、全く動かないアイテムにはつけても特に意味はありません(そのアイテムが、別の動くアイテムを生成するなら別ですが)。

実際のスクリプト

まずScriptableItemコンポーネントをアイテムに追加してください。
そしてScriptableItemの「Source Code」のところに以下のコードを丸ごと貼りつければOKです(抜けがないようにしてください)。

//何秒に一回周りのプレイヤーををチェックするか?
const PLAYER_CHECK_TICK_INTERVAL = 0.5;
//一度Ownerを変更したら何秒待つか?
const OWNER_CHANGE_TICK_INTERVAL = 3.0;
//周囲の何mのプレイヤーを探すか?
const PLAYER_FIND_DISTANCE = 5.0;

//初期化処理
$.onStart(() => {
    $.state.playerCheckTick = 0;
	$.state.ownerChangeWaitTick = 0;
});

$.onUpdate((deltaTime) => {
    $.state.ownerChangeWaitTick -= deltaTime;
    //前回のOwner変更から時間が十分経っていなければ何もしない
	if ($.state.ownerChangeWaitTick > 0) {
		return;
	}

    //一定時間ごとに周辺のプレイヤーをチェック
	$.state.playerCheckTick += deltaTime;
    if ($.state.playerCheckTick > PLAYER_CHECK_TICK_INTERVAL) {
		$.state.playerCheckTick -= PLAYER_CHECK_TICK_INTERVAL;

        //アイテム自身の位置
        let pos = $.getPosition();
        //周辺のプレイヤーのリスト
		const players = $.getPlayersNear(pos, PLAYER_FIND_DISTANCE);
        //プレイヤーが見つかったら
		if (players.length > 0) {
            //一番近いプレイヤーの距離
			let distance = Number.MAX_VALUE;
			let tempTargetPlayer = null;
            //各プレイヤーについて
			for (let i = 0; i < players.length; i++) {
				const player = players[i];
				const playerPos = player.getPosition();
                //このアイテムとの距離を測定
				const playerDistance = pos.clone().sub(playerPos).length();
                //一番近かったなら
				if (playerDistance < distance) {
                   //そのときの距離を保存し
					distance = playerDistance;
                   //Owner候補にしておく
					tempTargetPlayer = player;
				}
			}
            //最も近かったプレイヤーをOwnerにする
	        $.requestOwner(tempTargetPlayer);

            //一定の秒数、Owner変更が起きないようにする
	        $.state.ownerChangeWaitTick = OWNER_CHANGE_TICK_INTERVAL;
		}
    }
});

上記スクリプトの作例

https://cluster.mu/w/8f92ee86-1c8e-4d7b-81b5-e258d4d0b1cf
上のスクリプトを活かした、「あばれ箱」という箱が暴れるワールドです。
複数人プレイしている場合は、近くのハコのほうが動きがなめらかに見えるはずです。

※わかりやすくするために、上のスクリプトに「今Ownerが誰か」を表示する機能だけ加えています。

Discussion