MkItYs

MkItYs > ネットワークとサーバを作る > 

images

ネットワークとマルチプレイ 〜 シンプルな実装:Unity (Netcode)

images

ネットワークを介したマルチプレイの、シンプルな実装です。


images

概要


MRは、身の回りの現実に仮想のモノやヒトを投入しますーーそうなると、仮想現実をその場にいる人たち(家族・友人・仲間)と共有したい、という要望も出てきますよね。

そのためには、MRの利用者が仮想現実と相互作用する結果を、それぞれの利用者に、リアルタイムに反映させる必要があります。

これは、ネットワークを介した同期技術で実現できますがーーUnity にはその目的に使えるライブラリが複数あり、とくに公式にサポートされている同期技術に「Netcode 」があります。

実装


ここでは、Netcode を使った、とてもシンプルな実装を試しますーー

複数の利用者が、シーンにある自身のオブジェクト(ここではキューブ)を左右に移動させる、という単純なマルチプレイです。[※1]

まず、ネット同期のためのパッケージを追加します:

window > package manager
> add package: com.unity.netcode.gameobjects

シーンに、ネット管理とプレイヤ管理の空のオブジェクトをそれぞれ追加し、その役割のためのコンポーネント(スクリプトふくむ)を付与します:

hierarchy > SCENE

> NETWORK_MANAGER (GameObject)
  > add component: NetworkManager
  > add component: NetCtl (Script)
  > NetworkManager
    > unity transport (<- select transport)

> PLAYER (Cube: GameObject)
  > add component: NetworkObject
  > add component: NetworkTransform
  > add component: PlyCtl (Script)

プレイヤ管理のオブジェクトは、プレハブ化します(シーンからは削除)。そしてネット管理のオブジェクトから、それをプレイヤとして指定します:

project > assets

> PLAYER (<- hierarchy > SCENE > PLAYER)

hierarchy > SCENE

> NETWORK_MANAGER
  > NetworkManager
    > player prefab: PLAYER

コードはそれぞれ、次のようになります:

NetCtl.cs
using UnityEngine;
using Unity.Netcode;

public class NetCtl: MonoBehaviour {
  void OnGUI() {
    GUILayout.BeginArea(new Rect(0, 0, 100, 100));
    if (GUILayout.Button("H")) {NetworkManager.Singleton.StartHost();}
    if (GUILayout.Button("S")) {NetworkManager.Singleton.StartServer();}
    if (GUILayout.Button("C")) {NetworkManager.Singleton.StartClient();}
    GUILayout.EndArea();
  }
}
PlyCtl.cs[※2]
using UnityEngine;
using Unity.Netcode;

public class PlyCtl: NetworkBehaviour {
  public float u = 0.01f;

  void Update() {
    if (IsOwner) {
      if (Input.GetKey(KeyCode.RightArrow)) {
        Mov_ServerRpc( u);
      }
      if (Input.GetKey(KeyCode.LeftArrow)) {
        Mov_ServerRpc(-u);
      }
    }
  }

  [ServerRpc]
  void Mov_ServerRpc(float n) {
    Vector3 v;
    v = new Vector3(n, 0, 0);
    transform.position = transform.position + v;
  }
}

アプリをビルドし、複製して複数個に増やし、ぜんぶを起動します。それぞれの画面で、ひとつをサーバ(「S 」)に、そのほかをクライアント(「C 」)にするよう、ボタンを押します。

クライントの画面では、左右のキーを使い、プレイヤのオブジェクト(キューブ)を動かしますーーすべてのアプリの画面に、結果がリアルタイムに反映されることを確認できるはずです。[※3][※4]


images

※1
参考:
https://docs-multiplayer.unity3d.com/netcode/current/tutorials/get-started-ngo/
https://qiita.com/mrt/items/e2a971260479385cd928
※2
このコードの「[ServerRpc] 」以降は、クライアントからサーバに要求する、ネットワーク通信(RPC )の内容を記述する箇所ですーークライアントは、サーバがもつオブジェクトを操作するよう、データともに依頼します。サーバはそのデータをもとに、自身がもつオブジェクト群を依頼どおりに操作します(なおサーバに依頼するための関数は、「〜ServerRpc 」という名前で終わらせる必要があります)。
※3
ボタン「H 」を押すと、そのアプリは<ホスト>になりますーーホストは、サーバ兼クライアントです。運用で使うことはほとんどないと思いますが、アプリを(ビルドせずに)エディタから試験をするのに利用できます(単体のアプリで、サーバとクライアントの動作を確認できる)。
※4
この実装では物理的なネットワークを介していませんが、動作そのものは、インターネット標準のプロトコル(TCP/IP)に準じていますーーサーバは、ローカルループバックアドレス(127.0.0.1 )のポート(7777)で要求を待ち受け、クライアントは、そのポートに要求を送っています(ただUI上はそうみせていても、実装上はローカルなプロセス間通信を使っているかもしれませんがーーUNIXならドメインソケット、Windows なら名前つきパイプ、など)。

補足


それぞれの利用者が勝手に行動するのでなく、利用者の行動が、シーンに影響を与えるようにしてみます。

シーンに物理演算をほどこしたオブジェクトを追加し、プレイヤのオブジェクトが、それに作用する(押せる)ようにします:

hierarchy > SCENE

> PLANE (Plane: GameObject)
  > Transform: y: -0.5

> TARGET_OBJECT (Capsule: GameObject)
  > Transform: x: 1
  > add component: Rigidbody

project > assets

> PLAYER
  > add component: Rigidbody
  > Rigidbody
    > use gravity: <nil>
    > is kinematic: <yes>

実行すると、どのプレイヤからも、ターゲットのオブジェクトに作用する(押せる)ことを確認できるはずですーー単純な協力プレイの実現ですね。


images
images