チュートリアル: Pong - HLAPIバージョン
HLAPIを使ったAR卓球ゲームの作成方法。
このチュートリアルについて
このチュートリアルでは、HLAPIを使用したマルチプレイヤーAR卓球ゲームの作成方法を説明します。参照するシーンやコード、アセットは、ARDKの使用例のプロジェクト <https://lightship.dev/account/downloads>`__ の Assets/ARDKExamples/PongHLAPI
フォルダを参照してください。
このチュートリアルの前に、 HLAPIの概要 と HLAPIに関するよくある質問 のページを読むことをお勧めします。
PongHLAPIは、 Pongのベーシック版デモ(低レベルバージョン) と基本的な部分は同じですが、ARDKの高レベルネットワークAPI(HLAPI)を活用するために一部変更を行っています。まず、メッセージを管理するクラスである MessagingManager
は廃止されました。低レベル版Pongで MessagingManager
を通して渡されていたデータはすべて、UnreliableTransformBroadcastPackerやMessageStreamReplicatorなどのHLAPIオブジェクト経由で渡されるようになりました。プレイヤー間で共有されるフィールドは、NetworkedFieldインスタンスを使用して自動的に設定し、同期を行うことができます。また、プレイヤーごとにローカルに生成され、メッセージによって同期されていたオブジェクトは、NetworkSpawnerを使用して生成できるようになりました。
使用開始
ネットワークグループとデータハンドラ
各共有データは、それぞれ固有の <
NetworkGroup, NetworkedDataHandler >
の組み合わせに存在し、各クライアントは通信前にこれを作成、オープンし、登録します。 NetworkGroup
は複製されるエンティティであり、それぞれのプロパティに NetworkedDataHandler
があるとお考えください。たとえば、"Player"グループには体力(health)やエネルギー(energy)のデータハンドラを登録し、"Enemy"グループには攻撃力のデータハンドラを登録することができます。
ネットワークセッションのすべてのピアで <NetworkGroup, NetworkedDataHandler>
の定義が一致していれば、HLAPIはデータの送受信を処理することができます。
例:
// Code snippet from GameController.cs private IHlapiSession _manager; private IAuthorityReplicator _auth; private bool _isHost; private IPeer _self; private void OnDidConnect(ConnectedArgs connectedArgs) { _isHost = connectedArgs.IsHost; _self = connectedArgs.Self; // 19244 is an arbitrary magic number for this HlapiSession's message tag // (so don't use 19244 again in the same project). _manager = new HlapiSession(19244); // Similarly, 4321 is an arbitrary magic number for this NetworkGroup // (so don't use it again for a different group). var group = _manager.CreateAndRegisterGroup(new NetworkId(4321)); // An AuthorityReplicator is a type of NetworkedDataHandler, // and thus created using a reference to a NetworkGroup. _auth = new GreedyAuthorityReplicator("pongHLAPIAuth", group); // Only one peer should try to claim authority. In this example, it's easy // for that peer to be the host, since there is only a single host. // More explanation on Authority can be found in the HLAPI FAQ page. _auth.TryClaimRole(_isHost ? Role.Authority : Role.Observer, () => {}, () => {}); // More code... }
上記のスニペットにあるように、グループやデータハンドラはHlapiSessionインスタンスから手動で構築できます。役に立つように、NetworkedBehaviour
コンポーネントを持つネットワークスポーンされたオブジェクトでは、グループやデータハンドラが指定された HlapiSession
を自動的に作成し、管理します。
アップデートの送信:
データの更新を送信するには、HlapiSession.SendQueuedDataメソッドを呼び出します。このメソッドは、最新のデータがフレームごとに1回送信されるように Update()
ループで呼び出すことができます。各 HlapiSession
は、作成されたグループとデータハンドラにのみ影響します。
private void Update() { if (_manager != null) _manager.SendQueuedData(); // More code... }
NetworkedField
低レベル版Pongで明示的にシリアル化されメッセージとして送信されたデータの一部は、NetworkedFieldオブジェクトとして複製されます。たとえば、スコア( string
)、ゲーム開始フラグ( bool
)、フィールドの位置( Vector3
)があります。
ネットワークフィールド
// Code snippet from GameController.cs private INetworkedField<NetString> _scoreText; private Text score; private void OnDidConnect(ConnectedArgs connectedArgs) { // ... // Continuing from code snippet above... // A NetworkedDataDescriptor defines the sending/receiving rules for data, // as well as network protocol. This descriptor defines that only the peer // with Authority can make changes to the field, and all peers that are // Observers will see the change. var authToObserverDescriptor = _auth.AuthorityToObserverDescriptor(TransportType.ReliableUnordered); // ... // Create a NetworkedField using the NetworkedDataDescriptor and the NetworkGroup _scoreText = new NetworkedField<string>("scoreText", authToObserverDescriptor, group); // Events are also fired upon the field changing _scoreText.ValueChanged += OnScoreDidChange; // More code... } private void OnScoreDidChange(NetworkedFieldValueChangedArgs<string> args) { score.text = args.Value.GetOrDefault(); }
役割が Authority
のピアがフィールドの値を設定する場合はいつでも呼び出すことができます。
_scoreText.Value = string.Format("Score: {0} - {1}", RedScore, BlueScore);
GameController.cs
の OnDidConnect
のメソッドでも同様に、フィールドの位置やゲーム開始フラグにフィールドが設定されます。
注釈
booleanフィールドを複製するには、 bool
を byte
としてシリアライズし、受信時に bool
に変換する必要があります。ARDKには、一般的なタイプのシリアライザーがデフォルトで付属していますが ( ItemSerializers 名前空間を参照) GlobalSerializer.RegisterItemSerializer()
を使用して BaseItemSerializer<T>
が作成されていれば、どのようなタイプでもネットワーク化することができます。
UnreliableBroadcastTransformPacker
UnreliableBroadcastTransformPackerも NetworkedDataHandler
の一種であり、これを使用して複数のデバイス間で自動的に UnityEngine.Transform
の同期を行うことができます。パッカーが設定されると、 HlapiSession.SendQueuedData()
が呼び出される度に指定の変換が更新されるため、オブサーバーが追加設定する必要はありません。
// Code from BallBehaviour.cs // The BallBehaviour class extends NetworkedBehaviour, so it uses its Owner to access the its Auth // and Group (which both belong to an internal HlapiSession). The UnreliableBroadcastTransformPacker // works with any authority and data handlers, regardless of how they were constructed. protected override void SetupSession(out Action initializer, out int order) { initializer = () => { var auth = Owner.Auth; // If a peer with a role other than Authority changes the transform managed by the packer, // the changes will be seen locally but not broadcast to other peers. var descriptor = auth.AuthorityToObserverDescriptor(TransportType.UnreliableUnordered); // Simply creating the UnreliableBroadcastTransformPacker will set up broadcasting/receiving // the Position property of the given Transform (any combination of Position, Rotation, // and Scale can be replicated). new UnreliableBroadcastTransformPacker ( "netTransform", transform, descriptor, TransformPiece.Position, Owner.Group ); }; // More code... }
MessageStreamReplicator
MessageStreamReplicator( NetworkedDataHandler
とも呼ばれる)は、ピアがセッション内のピアにメッセージ(シリアライズ可能な任意の型)を送信することができます。 MultipeerNetworking.SendDataToPeer
メソッドとは異なり、 MessageStreamReplicator
には NetworkGroup
があるため、メッセージタグは必要ありません。
// Code snippet from GameController.cs private void OnDidConnect(ConnectedArgs connectedArgs) { // ... // Continuing method from second code snippet... _hitStreamReplicator = new MessageStreamReplicator<Vector3> ( "hitMessageStream", _arNetworking.Networking.AnyToAnyDescriptor(TransportType.ReliableOrdered), group ); _hitStreamReplicator.MessageReceived += (args) => { Debug.Log("Ball was hit"); if (_auth.LocalRole != Role.Authority) return; _ballBehaviour.Hit(args.Message); }; }
メッセージの送信
// Code from Update method in GameController.cs // After some code to calculate the bounce direction vector... // Send the hit message to the Authority (the host in this case) _hitStreamReplicator.SendMessage(bounceDirection, _auth.PeerOfRole(Role.Authority));
ネットワークスポーンのクラス
以下の3つのクラスは、複数のピアに対して同時にオブジェクトをスポーンすることや、スポーン後のそのオブジェクトの動作の設定に必要です。
NetworkedUnityObjectコンポーネントをオブジェクトにアタッチすると、ネットワークスポーンが有効になります。
NetworkedBehaviourコンポーネントは、MonoBehaviourのように、NetworkedUnityObjectのスポーン後の動作を決定します(実際にはMonoBehaviourを継承)。このコンポーネントは、1つのGameObjectに対して複数アタッチすることができます。
最後に、NetworkSpawnerは、
GameObject
のスポーンを実際に処理する静的なクラスです。
NetworkedBehavioursの拡張
前述のように、 NetworkedBehaviour
が作成されると、自身の NetworkGroup
と、内部で管理されている HlapiSession
が開きます。そのため、 NetworkedBehaviour
を継承するクラスはいずれも、抽象的な SetupSession
メソッドを実装するだけです。
オブジェクトがスポーンされたときに呼び出されるコールバックを指定します。
そのコールバックを呼び出す相対的な順序を指定します(ある動作が他の動作より先に初期化されなければならない場合に有効です)。ネットワークスポーンの設定と使用に関する詳細については、 Hlapiのネットワークスポーン ページを参照してください。
PongHLAPIでは、 PlayingFieldBehaviour
と PlayerAvatarBehaviour
はいずれも、シンプルな NetworkedBehaviours
で、権限を持つピアによってそれらの値が変更されると、位置や回転が複製されるように設定されています( NetTransform
コンポーネントは、その動作のみが必要な場合に実装するヘルパーコンポーネントです)。
[RequireComponent(typeof(AuthBehaviour))] public class PlayingFieldBehaviour: NetworkedBehaviour { protected override void SetupSession ( out Action initializer, out int order ) { initializer = () => { var auth = GetComponent<AuthBehaviour>(); new UnreliableBroadcastTransformPacker ( "netTransform", transform, auth.AuthorityToObserverDescriptor(TransportType.UnreliableUnordered), TransformPiece.Position, Owner.Group ); }; order = 0; } }
権限を持つピアにのみ発生し、監視しているピアには発生しない動作がある場合はまず、 NetworkedBehaviour
クラスのメソッドで Owner.Authority.LocalRole
の値を確認する必要があります。
// Code snippet from GameController.cs private void InstantiateObjects(Vector3 position) { // ... // The playerPrefab is spawned for all players _player = playerPrefab.NetworkSpawn ( _arNetworking.Networking, position + startingOffset, Quaternion.identity, Role.Authority ).gameObject; // Only the host should spawn the remaining objects if (!_isHost) return; // Instantiate the playing field at floor level _playingField = playingFieldPrefab.NetworkSpawn ( _arNetworking.Networking, position, Quaternion.identity ) .gameObject; // More code... }