チュートリアル: プレイヤーのキャラクターを同期する
このチュートリアルでは、ルームに参加したプレイヤーがそれぞれ自分のキャラクターを動かし、ほかのプレイヤーの画面にも同じように表示される、小さなルームを作ります。
- 接続すると、自分のキャラクターが 1 体生成されます。
- 矢印キー(WASD)で自分のキャラクターを動かすと、ほかのプレイヤーの画面でも滑らかに動きます。
- スペースキーを押すと、全員の画面でそのキャラクターが一瞬大きくなります(RPC)。
サーバータイプは sync を使います。ルームのルールを書く必要はありません。サーバータイプの違いは構成と選び方を参照してください。
作業の流れ
| 手順 | 作業する場所 | 作るもの |
|---|---|---|
| 1. ネームスペースを設定する | GS2 | Realtime のネームスペース設定 |
| 2. キャラクターの Prefab を作る | Unity Editor | 移動と RPC のスクリプトを付けた Prefab |
| 3. ルームに接続する | Unity Editor | Prefab Catalog と、接続して自分のキャラクターを生成するシーン |
| 4. 動作を確かめる | Unity(2 クライアント) | — |
| 5. 公開に向けて制限する | GS2 | オブジェクトカタログ |
GS2 SDK for Unity を導入した Unity プロジェクト(Unity 2021.3 以降)で作業します。同期のコンポーネントは SDK に含まれているので、追加のパッケージは必要ありません。
1. ネームスペースを設定する
GS2-Realtime のネームスペースで、サーバータイプを指定します。
| 設定する内容 | 設定項目 | 指定する値 |
|---|---|---|
| サーバータイプ | serverType | sync |
ルームの用意(GS2-Matchmaking と RoomWant)は、ほかのサーバータイプと同じです。
2. キャラクターの Prefab を作る
スクリプトを用意する
自分のキャラクターを動かすスクリプトです。Transform を動かすだけで、NetworkTransform がほかの参加者へ位置を送ります。スペースキーで、全員に RPC を送ります。
using Gs2.Realtime.Sync;
using UnityEngine;
// NetworkEntity の Controlled Behaviours に登録し、自分のキャラクターでだけ動かす
public sealed class PlayerMover : MonoBehaviour
{
[SerializeField] float speed = 3f;
void Update()
{
var move = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"));
// Transform を動かすだけで、NetworkTransform がほかの参加者へ送る
transform.position += move.normalized * (speed * Time.deltaTime);
if (Input.GetKeyDown(KeyCode.Space))
GetComponent<NetworkEntity>().Rpc(nameof(PlayerEmote.Wave), RpcTarget.All);
}
}RPC で呼ばれるメソッドには [NetworkRpc] を付けます。IEnumerator を返すと、コルーチンとして実行されます。
using System.Collections;
using Gs2.Realtime.Sync;
using UnityEngine;
public sealed class PlayerEmote : MonoBehaviour
{
// 全員の画面で、そのキャラクターが一瞬大きくなる
[NetworkRpc]
public IEnumerator Wave(NetworkRpcInfo info)
{
Debug.Log("Wave from connection " + info.Sender);
transform.localScale = Vector3.one * 1.5f;
yield return new WaitForSeconds(0.3f);
transform.localScale = Vector3.one;
}
}info.Sender は、RPC を送ったプレイヤーの接続 ID です。サーバーが設定するので、送った側が偽ることはできません。
Prefab を作る
キャラクターの見た目になる Prefab(Capsule など)を作り、ルートに次のコンポーネントを追加します。
コンポーネント 設定 NetworkEntity Schema は空のまま(位置だけを同期するため) NetworkTransform Position Axes は All、Rotation Axes はNonePlayerMover 上のスクリプト PlayerEmote 上のスクリプト NetworkEntity > Controlled Behaviours に
PlayerMoverを登録します。PlayerMoverは、自分が所有するキャラクターでだけ有効になります。登録しないと、自分の画面にあるほかのプレイヤーのキャラクターにも、自分のキー入力が適用されてしまいます。
位置と回転は SDK に標準のフィールドがあるので、同期スキーマのアセットがなくても同期できます。HP のような独自の値を同期するときは、独自の値を表示するを参照してください。
3. ルームに接続する
NetworkWorld を置く
- Project ウィンドウの Create > GS2 > Realtime > Prefab Catalog でカタログを作ります。
- カタログの Prefabs に、Id
1と手順 2 の Prefab を登録します。Prefab ID はオブジェクトの種類を表す番号で、ほかのクライアントはこの番号から表示する Prefab を選びます。 - シーンに空の GameObject を作り、NetworkWorld を追加して、Prefab Catalog にカタログを指定します。
- 地面になる Plane と、上から見下ろす Camera を置きます。
接続スクリプトを追加する
NetworkWorld と同じ GameObject に NetworkSession と、次のスクリプトを追加します。ルームの接続情報(IP アドレス・ポート・暗号鍵)とアクセストークンは、ゲームサーバーへの接続情報を取得の方法で取得して渡します。
using Gs2.Realtime.Sync;
using UnityEngine;
[RequireComponent(typeof(NetworkWorld), typeof(NetworkSession))]
public sealed class PlayerRoomConnection : MonoBehaviour
{
const uint PlayerPrefab = 1; // Prefab Catalog に登録した Id
NetworkWorld world;
NetworkSession session;
// イベントは一度だけ登録します(接続のたびに登録すると、再接続でハンドラーが重複します)
void Awake()
{
world = GetComponent<NetworkWorld>();
session = GetComponent<NetworkSession>();
// ルームの状態を反映したら、自分のキャラクターを生成する。
// 切断するとキャラクターは削除されるので、再接続のたびに作り直す
session.Ready += () => world.Instantiate(
PlayerPrefab,
new Vector3(Random.Range(-3f, 3f), 0, Random.Range(-3f, 3f)),
Quaternion.identity);
session.Disconnected += reason => Debug.Log("切断しました: " + reason);
}
public void Connect(string accessToken, string ipAddress, int port, string encryptionKey)
{
session.Connect(accessToken, ipAddress, port, encryptionKey);
}
}ポイントは次の 3 つです。
- 自分のキャラクターは
world.Instantiateで作ります。 Unity のInstantiateで作った GameObject は、ほかの参加者には共有されません。world.Instantiateの戻り値は、その場で自分の所有(IsMine)として動かせます。ルームが生成を確定するまでの移動や RPC は保留され、確定後に送られます。 - ほかのプレイヤーのキャラクターは NetworkWorld が作ります。 ルームから届いた生成を見て、カタログの Prefab から GameObject を作り、届いた位置へ補間して動かします。
- 接続すると、その時点の状態が届きます。 あとから参加したプレイヤーにも、参加済みのプレイヤーのキャラクターがその時点の位置で表示されます。反映が終わると
Readyが呼ばれます。
4. 動作を確かめる
同じルームへ、2 つのクライアントから接続します。同じ userId で接続すると先の接続が置き換わるため、別々の userId のアクセストークンを使ってください。
- 両方の画面に、2 体のキャラクターが表示されることを確かめます。
- 片方でキャラクターを動かし、もう片方の画面でも同じキャラクターが滑らかに動くことを確かめます。もう片方のキー入力では、自分のキャラクターだけが動きます。
- 片方でスペースキーを押し、両方の画面でそのキャラクターが一瞬大きくなることを確かめます。
- 片方を切断し、もう片方の画面からそのキャラクターが消えることを確かめます。接続し直すと、新しいキャラクターが生成されます。
5. 公開に向けて制限する
ここまでの設定では、ルームはプレイヤーが生成する Prefab や呼ぶ RPC を検査しません。改造したクライアントからの生成や RPC を制限するには、ネームスペース設定の syncSetting.objectCatalog にオブジェクトカタログを設定します。
- Prefab Catalog アセットを右クリックし、Copy Object Catalog JSON を選びます。登録した Prefab の同期するフィールドと RPC の ID が、JSON としてクリップボードにコピーされます。
- その JSON を
syncSetting.objectCatalogに設定します。
設定すると、登録した Prefab だけを生成でき、methods に登録した RPC だけを所有者が呼べるようになります。RPC の ID はメソッド名から決まるので、メソッド名を変えたら JSON を作り直してください。
次に読むもの
- Unity クライアント:操作権の貸し出し、ルームイベント、カスタムプロパティ、マスタークライアント、Interest Group、UDP、再接続の扱い。
- Photon からの移行ガイド:PUN2 の API との対応表と移行の手順。
- チュートリアル: サーバーで HP を管理する敵を作る:ルールでオブジェクトを生成・更新するサーバータイプ
stateMachineの使い方。 - チュートリアル: ロールバック式同期でキャラクターを動かす:位置をサーバーが計算し、自分のキャラクターを予測して動かす方法。