チュートリアル: Pong - メッセージの送受信

ARDKの機能を使用して作った、ARマルチプレイヤー版『Pong』のUnityプロジェクト例です。このチュートリアルでは、プロジェクトを正しく機能させるためのUnity上での各ステップやC#スクリプトの使用法をご確認いただけます。この使用例では低レベルメッセージを使ってプレイヤー間のデータ送信を行っています。このプロジェクトの別バージョンでは、プレイヤー同期用のメッセージ送受信プロセスを能率化する高レベルAPIオブジェクト(HLAPI)のセットアップおよび使用例が示されています (こちら)

MessagingManagerによるメッセージの送受信

各スクリプトがそれぞれメッセージを受信して処理することも可能ですが、単体のメッセージ処理マネージャを用いれば多数のクラス間で整理が行えます。MessagingManagerMonobehaviour ではありません。その代わり、ネットワークイベントおよびメソッド呼び出しによってすべてのメソッドを処理します。

セットアップと参考情報

MessagingManager のリファレンスは主にネットワーク関連であり、 MessagingManager はローカルの GameControllerBallBehaviour へのリファレンスを保持して、メッセージが受信されたときにイベントへ通知します。

/// A manager that handles outgoing and incoming messages
public class MessagingManager
{
  // Reference to the networking object
  private IMultipeerNetworking _networking;

  // References to the local game controller and ball
  private GameController _controller;
  private BallBehaviour _ball;

  private readonly MemoryStream _builderMemoryStream = new MemoryStream(24);

MessagingManagerの初期化

MessagingManagerGameController によって初期化され、関連する情報を渡します。サブスクライブしているネットワークイベントは IARNetworking.Networking.PeerDataReceived のみです。メッセージタグの一貫性を保つために列挙型が使用されます。

// Enums for the various message types
private enum _MessageType:
  uint
{
  BallHitMessage = 1,
  GoalScoredMessage,
  BallPositionMessage,
  SpawnGameObjectsMessage
}

// Initialize manager with relevant data and references
internal void InitializeMessagingManager
(
  IMultipeerNetworking networking,
  GameController controller
)
{
  _networking = networking;
  _controller = controller;

  _networking.PeerDataReceived += OnDidReceiveDataFromPeer;
}

// After the game is created, give a reference to the ball
internal void SetBallReference(BallBehaviour ball)
{
  _ball = ball;
}

送信メッセージ

ローカルプレイヤーが他のプレイヤーにメッセージを送信する際、これらのメソッドの1つが呼び出されます。各メッセージにはタグが含まれているため、受信側のプレーヤーはデータの解析方法を知ることができます。BroadcastData はネットワークセッション内の他のすべてのピアにデータを送信し、 SendDataToPeer は1つのピアにデータを送ります。 ヘルパーメソッド SerializeVector3Vector3``を ``byte[] に変換するために使用されます。

// Signal to host that a non-host has hit the ball, host should handle logic
internal void BallHitByPlayer(IPeer host, Vector3 direction)
{
  _networking.SendDataToPeer
  (
    (uint)_MessageType.BallHitMessage,
    SerializeVector3(direction),
    host,
    TransportType.UnreliableUnordered
  );
}

// Signal to non-hosts that a goal has been scored, reset the ball and update score
internal void GoalScored(String color)
{
  var message = new byte[1];

  if (color == "red")
    message[0] = 0;
  else
    message[0] = 1;

  _networking.BroadcastData
  (
    (uint)_MessageType.GoalScoredMessage,
    message,
    TransportType.ReliableUnordered
  );
}

// Signal to non-hosts the ball's position every frame
internal void BroadcastBallPosition(Vector3 position)
{
  _networking.BroadcastData
  (
    (uint)_MessageType.BallPositionMessage,
    SerializeVector3(position),
    TransportType.UnreliableUnordered
  );
}

// Spawn game objects with a position and rotation
internal void SpawnGameObjects(Vector3 position)
{
  _networking.BroadcastData
  (
    (uint)_MessageType.SpawnGameObjectsMessage,
    SerializeVector3(position),
    TransportType.ReliableUnordered
  );
}

メッセージの受信

一方、ネットワークからメッセージが受信されるたびにコールバック OnDidReceiveDataFromPeer が呼び出されます。このメソッドはタグに応じてメッセージを処理し、 GameControllerBallBehaviour へのリファレンスを使用してゲームロジックを適切に処理します。SerializeVector3 と同様、 byte[]Vector3 にコンバートするヘルパーメソッド DeserializeVector3 があります。

private void OnDidReceiveDataFromPeer(PeerDataReceivedArgs args)
{
  var data = args.CopyData();
  switch ((_MessageType)args.Tag)
  {
    case _MessageType.BallHitMessage:
      _ball.Hit(DeserializeVector3(data));
      break;

    case _MessageType.GoalScoredMessage:
      if (data[0] == 0)
      {
        Debug.Log("Point scored for team blue");
        _controller.BlueScore += 1;
      }
      else
      {
        Debug.Log("Point scored for team red");
        _controller.RedScore += 1;
      }

      _controller.score.text =
        string.Format
        (
          "Score: {0} - {1}",
          _controller.RedScore,
          _controller.BlueScore
        );

      break;

    case _MessageType.BallPositionMessage:
      _controller.SetBallLocation(DeserializeVector3(data));
      break;

    case _MessageType.SpawnGameObjectsMessage:
      Debug.Log("Creating game objects");
      _controller.InstantiateObjects(DeserializeVector3(data));
      break;

    default:
      throw new ArgumentException("Received unknown tag from message");
  }
}

削除

このオブジェクトを削除する際は、ネットワークイベントからサブスクライブ解除を行います。

// Remove callback from networking object on destruction
internal void Destroy()
{
  _networking.PeerDataReceived -= OnDidReceiveDataFromPeer;
}

シリアライズ

ネットワークへのデータ送信には byte[] のデータが必要です。ARDKでは様々なオブジェクトタイプをシリアライズおよびデシリアライズしてメッセージ送信を円滑化するためのヘルパーを利用できます。このプロジェクトは位置データに関して主に Vector3 を使用するため、 Vector3 のシリアライズおよびデシリアライズには2つのヘルパーが存在します。

// Helper to serialize a Vector3 into a byte[] to be passed over the network
private byte[] SerializeVector3(Vector3 vector)
{
  _builderMemoryStream.Position = 0;
  _builderMemoryStream.SetLength(0);

  using (var binarySerializer = new BinarySerializer(_builderMemoryStream))
    Vector3Serializer.Instance.Serialize(binarySerializer, vector);

  return _builderMemoryStream.ToArray();
}

// Helper to deserialize a byte[] received from the network into a Vector3
private Vector3 DeserializeVector3(byte[] data)
{
  using(var readingStream = new MemoryStream(data))
    using (var binaryDeserializer = new BinaryDeserializer(readingStream))
      return Vector3Serializer.Instance.Deserialize(binaryDeserializer);
}

前のページ: ゲームロジックとARイベント