著者: 禅とコンピュータープログラミングの芸術
1 はじめに
2019年、IntelやFacebookなどが共催したGDCカンファレンスで、Unity Technologiesが新ブランドUnity Game Development Platform(UGDP)を立ち上げることが発表された。このプラットフォームには、Unreal Engine 4、Unreal Engine 4、およびネイティブ Unity エンジンのサポートが含まれます。このプラットフォームに基づいて、Unity Technologies はリアルタイム マルチプレイヤー オンライン ゲーム サービスである Photon Services を開始しました。これには次の利点があります。
- Windows/Mac/Linux/IOS/Androidをサポート
- プロジェクトの規模に関係なく、完全に無料
- 強力な拡張性、数百万人の同時ユーザーをサポート可能
- WebGL/HTML5/PWAをサポート
- クラウドまたはローカルサーバーに迅速にデプロイ可能
これらの利点に基づいて、Unity Technologies の開発者は主に次の機能を提供する Photon SDK の完全なセットを開発しました。 - ゲームサーバーとクライアント間の通信
- ユーザー役割の同期
- 物理エンジン
- サウンドエンジン
- バディシステム
- グループ化システム
- 多言語サポート
- チャットシステム
- データストレージ
UGDP の目標は、ゲーム分野における包括的なクラウド開発プラットフォームになることです。GDCカンファレンスで公開されたビデオでは、今後ますます多くのゲームクリエイターがこのプラットフォームを開発に利用するだろうとも指摘されていた。Photon Unity Networking は、Photon Services に基づいて開発されたリアルタイム マルチプレイヤー オンライン ゲーム開発ソリューションの完全なセットです。これを使用して、独自のオンライン仮想現実ゲームをすばやく構築できます。
Photon Services をさらに改良するために、この記事では、Photon Services の関連する特徴と機能を次の順序で徐々に説明します。Photon Services の主な機能は次のとおりです。 - サービス モデル: Photon Services はクラウド ホスト型のサービス モデルを提供しており、顧客は優れたサービス エクスペリエンスを得るためにサーバーのランニング コストのみを支払う必要があります。
- 開発の敷居が低い: Photon Services はシンプルで理解しやすい豊富な API を提供するため、開発者は独自のゲーム ロジックを簡単に実装できます。
- 超高同時実行性: Photon Services は、単一マシン上で数百万レベルの同時実行性をサポートし、大規模なゲーム チームのビジネス ニーズを満たす分散クラスター アーキテクチャも提供します。
- 没入型ゲーム体験: Photon Services は、クロスプラットフォーム サポート (Windows/Mac/Linux/IOS/Android)、WebGL/HTML5/PWA、リアルタイム インタラクティブ チャット、多言語機能などの没入型ゲーム体験を提供します。
- データセキュリティ: Photon Services は、データの完全性、プライバシー、可用性を確保するために、データストレージと送信暗号化機能を提供します。
- 無料トライアル: Photon Services は無料のトライアル版を提供しており、開発者は料金を請求することなく Photon Services のすべての機能を体験できます;
次に、Photon Services の各機能モジュールについて詳しく説明します。
2. 基本的な概念と用語の説明
1. ユーザーの役割管理(認証・認可)
Photon Services では、各ユーザーに一意の ID が割り当てられます。ユーザーがサーバーに接続すると、サーバーは認証プロセスを通じてユーザーの有効な ID を確認します。認証が完了すると、サーバーはさまざまなデバイスを区別するための識別子としてトークンの文字列をユーザーに与えます。このプロセスは、多くの場合「認証」と呼ばれます。
サーバーは、認証および認可メカニズムを通じて、各ユーザーが特定のリソースにアクセスする権限を持っているかどうかを判断できます。各クライアントは、ユーザー ID を宣言し、対応するトークンを添付して、サーバーにリクエストを送信する必要があります。サーバーがトークンが正しいことを確認すると、ユーザーはリソースへのアクセスが許可されます。
サーバーの認証および認可モジュールは、次の 2 つのサブモジュールで構成されます。
- ユーザー登録: ユーザーが初めてログインするとき、ゲームを通常にプレイする前にアカウントを作成する必要があります。ユーザー登録には通常、ユーザー名、パスワード、電子メール アドレス、携帯電話番号の 2 つの側面が含まれます。ユーザー名とパスワードはユーザーの身元を識別および認証するために使用され、電子メールと携帯電話番号はパスワードの取得またはユーザーの身元の確認に使用できます。
- ユーザーログイン: ユーザーはユーザー名とパスワードを使用してPhoton Serverにログインし、ID認証を完了できます。サーバーはユーザー名とパスワードを検証した後、トークンを生成し、さまざまなデバイスを区別するための識別子としてクライアントに送信します。
ユーザーがログインした後の認証情報は、ユーザーがログアウトするまでサーバー上の一時データベースに保存されます。
2.ユーザー役割の同期
ユーザーがゲーム クライアントにログインするとき、ゲーム サーバー上の他のプレイヤーの位置情報とステータス情報を見つける必要があります。このプロセスは「役割の同期」と呼ばれます。
Photon Services は、ゲーム クライアントが位置、ステータスなどを含むすべてのプレイヤー情報を自動的に取得できるようにするキャラクター同期フレームワークを提供します。クライアントは関数を呼び出すだけで、すべてのプレーヤーの最新ステータスを受信できます。
各プレーヤーの最新のステータスは、コールバックを通じてクライアントに返すことができます。このようにして、クライアントは、プレイヤーの位置、視野、角度などの情報に基づいて、要件を満たす仮想世界をレンダリングできます。
3.物理エンジン
キャラクター同期のもう 1 つの重要な機能は、サーバーからの物理イベントにリアルタイムで応答することです。たとえば、サーバーは、プレーヤーがオブジェクトを撃ったことを示すメッセージをブロードキャストし、エンジンに結果を計算して応答するよう要求する場合があります。
Photon Services の物理エンジンは、高性能で完全な物理エンジン ライブラリです。剛体の動き、衝突検出、合力など、さまざまなタイプのゲーム物理動作をサポートするように設計されています。
物理エンジンは、都市などのオープンワールドを作成するために使用することも、e スポーツ競技会での小道具の動きなどのレベルのインテリアを作成するために使用することもできます。
4. サウンドエンジン
サウンド エンジンは、BGM、効果音、オーディオ トラックの切り替え、3 次元効果音など、さまざまなゲーム効果音の処理を担当します。Photon Services のサウンド エンジンは OpenAL API を使用して実装されており、さまざまなオーディオ形式とコーデックをサポートしています。
サウンド エンジンを使用してクライアント上でローカルのサウンド効果ファイルを再生し、ゲーム画面のパフォーマンスを向上させることもできます。
5.フレンドシステム
Photon Services は、プレイヤーが接続を確立し、ゲーム内のデータやリソースを共有できるフレンド システムを提供します。サーバーは各プレイヤー間の関係を記録し、クライアントに送信します。クライアントはフレンド システムを使用して、フレンドを検索、追加、削除したり、一緒にゲームをプレイするために友人を招待したりできます。
ゲーム内で独自のゲームプレイを実現するために、サーバーはルールに基づいた「チーム」を作成し、一緒に競争するように配置することもできます。各チームにはゲームを開始する統一マネージャーがいます。
6. グループ制度
グループ システムは、ゲーム内のプレーヤーを組織および管理するために使用できます。各プレイヤーは複数のグループに参加でき、各グループは複数のメンバーを持つことができます。グループ システムを使用すると公開ソーシャル サークルを作成でき、プレイヤーが共同作業しやすくなります。
グループ システムを使用してゲームに通知をブロードキャストし、コミュニティ全体に何が起こっているかを知らせることもできます。
7.多言語サポート
Photon Services の多言語サポート モジュールは、ゲーム開発者がさまざまな言語でゲームを提供するのに役立ちます。言語バージョンが異なるゲームの場合は、サーバー側で言語設定を行うだけで、クライアントは自動的に対応する言語に切り替わって表示されます。
8. チャットシステム
チャット システムは、ゲーム内のプレイヤーが互いに通信できるようにするリアルタイム コミュニケーション ツールです。Photon Services のチャット システムを使用すると、ユーザーはテキスト、音声、グラフィック、表現などのさまざまな形式で迅速かつ直接コミュニケーションを行うことができます。
チャット システムを使用してゲーム内の履歴データを記録することもでき、プレイヤーは過去の体験を振り返ることができます。
9. データストレージ
データ ストレージは重要な機能であり、ゲーム内のプレイヤーがアーカイブ、実績、プロパティなどのカスタマイズされたデータを保存できるようになります。Photon Services は、ファイルのアップロードとダウンロード、データ同期、データ クエリ、その他の機能を含む豊富なデータ インターフェイスを提供します。
さらに、ゲーム開発者はサーバーから統計データを取得して、プレイヤーの習慣や好みを理解することもできます。このデータは、ゲームプレイを最適化するために使用できます。
3. コアアルゴリズムの原理と操作手順
以下では、Photon Services の各機能モジュールについて詳しく説明し、その主要なテクノロジーとコア アルゴリズムの原則を紹介し、詳細なコード例を示します。
1.ユーザーロールの同期
ロールの同期は、Photon Services の最も基本的な機能です。ロールの同期に関係するアルゴリズムは次のとおりです。
- 差分同期: サーバーは絶対値の代わりに変更を送信することでネットワーク トラフィックを削減し、ゲームのパフォーマンスを向上させます。
- ファネル同期: サーバーはキャラクターの変更のみを送信し、ネットワーク消費を削減します。
- スムージング フュージョン: サーバーは「時間平均」法を使用して各クライアントの文字データを集計し、突然変異の影響を排除します。
手順:
- クライアントはサーバーに接続します。
- サーバーはクライアントに現在のロールの初期状態を伝えます。
- クライアントはロールのステータスを更新します。
- サーバーはキャラクターの変化量を計算します。
- サーバーはロールの変更量を送信します。
- クライアントはロールの変更量を受け取ります。
- クライアントはロールのステータスを更新します。
- 両側の役割が同期されるまで、このサイクルを繰り返します。
using Photon.Realtime;
using Photon.Pun;
public class MyCharacter : MonoBehaviourPun, IPunObservable
{
private Vector3 _position = Vector3.zero;
void Start()
{
if (photonView.IsMine)
{
// We are the local player, so we can request the other players via RPC call
}
}
[PunRPC]
void UpdateOtherPlayerPosition(Vector3 position)
{
this._position = position;
transform.position = position;
}
void Update()
{
photonView.RPC("UpdateOtherPlayerPosition", RpcTarget.Others, transform.position);
// Do some calculations based on new role state
...
}
}
上の例は、ロール同期の使用方法を示しています。クライアント スクリプトの Start() 関数は photonView.IsMine 条件を呼び出しますが、この条件はクライアントがメイン キャラクターである場合にのみ実行され、ローカル プレイヤーのキャラクター情報のみが要求され、同期されます。
キャラクターのステータスを更新する update() 関数は、photonView.RPC メソッドを使用し、UpdateOtherPlayerPosition 関数を呼び出して、リモート プロシージャ コールを通じて他のプレイヤーのキャラクターの位置を更新します。この機能は、リモート呼び出しを通じて、自身のキャラクターの位置情報を他のクライアントに送信します。
文字同期アルゴリズムの実装部分では、文字状態タイプとして Vector3 を使用しますが、実際には、クォータニオン、カラーなどの他のタイプも使用できます。さらに、PhotonView コンポーネントを使用してキャラクターの同期を拡張し、より複雑なシーンでキャラクターの同期を実現することもできます。たとえば、キャラクターにはさまざまな移動方法、攻撃能力、ターゲット アルゴリズムなどを持たせることができます。これらはすべてカスタム プロパティで定義でき、サーバー側はそれらすべてをクライアント側に同期します。
2.物理エンジン
物理エンジンは Photon Services のコア モジュールであり、多くのオンライン ゲームの基本モジュールです。Photon Services の物理エンジンは Bullet Physics API に基づいて開発されており、衝突、バネ、摩擦、減衰などの複雑な物理的動作をシミュレートできます。
Bullet Physics API は、リアルタイム コンピューティング用に特別に設計された高性能物理エンジンであり、信頼性の高い数学的パフォーマンスを備えており、ゲーム、仮想現実、ロボット制御、インタラクティブ メディアなどの分野に適しています。
手順:
- Rigidbody コンポーネントを作成します。
- 剛体をオブジェクトに追加し、質量、摩擦、抵抗などの適切なプロパティを割り当てます。
- 衝突ボディ (衝突シェイプ コンポーネント) を剛体に追加します。
- 相対位置を使用して剛体の位置と回転を同期します。
- (オプション) 物理マテリアルを適用します。
- (オプション) トリガーを追加します。
using UnityEngine;
using Photon.Pun;
public class MyCubeController : MonoBehaviourPun, IPunObservable
{
Rigidbody _rigidbody;
private float speed = 5f;
void Start()
{
_rigidbody = GetComponent<Rigidbody>();
if (!photonView.IsMine)
{
// We don't need to synchronize our movement, let's just move directly with interpolation
}
}
void Update()
{
if (photonView.IsMine && Input.GetKey(KeyCode.W))
{
_rigidbody.velocity += transform.forward * Time.deltaTime * speed;
}
else if (photonView.IsMine && Input.GetKey(KeyCode.S))
{
_rigidbody.velocity -= transform.forward * Time.deltaTime * speed;
}
if (photonView.IsMine && Input.GetKey(KeyCode.A))
{
_rigidbody.velocity -= transform.right * Time.deltaTime * speed;
}
else if (photonView.IsMine && Input.GetKey(KeyCode.D))
{
_rigidbody.velocity += transform.right * Time.deltaTime * speed;
}
if (_rigidbody!= null)
{
_rigidbody.velocity = Vector3.ClampMagnitude(_rigidbody.velocity, speed);
// Apply gravity force
_rigidbody.AddForce(new Vector3(0, -1, 0), ForceMode.Acceleration);
}
}
void FixedUpdate()
{
if (photonView.IsMine)
{
transform.position = _rigidbody.position;
transform.rotation = _rigidbody.rotation;
}
}
[PunRPC]
void SetVelocity(Vector3 velocity)
{
_rigidbody.velocity = velocity;
}
void OnTriggerEnter(Collider collider)
{
if (collider.GetComponent<PhotonView>() == null || collider.GetComponent<PhotonView>().IsMine)
{
return;
}
Debug.LogFormat("{0} entered trigger from {1}", gameObject.name, collider.gameObject.name);
}
}
上の例は、物理エンジンの使用方法を示しています。剛体コンポーネントを作成した後、適切な質量、摩擦、抵抗、その他の属性を剛体に割り当てます。キャラクターが速く走りすぎないように速度制限を設定します。キャラクターの入力コマンドを処理し、剛体の動きの方向に変換します。剛体の状態をサーバーに同期し、FixedUpdate() 関数で独自の変換を更新します。キャラクタ トリガー (OnTriggerEnter()) 関数は、リモート プロシージャ コール (SetVelocity()) を使用して、剛体の速度情報を他のクライアントに送信します。
物理エンジンは、交差する壁、階段、屋根、部屋の隔離エリア、トリガーなどの複雑な物理シーンを処理するために使用することもできます。これらのシーンには、正確なシミュレーションを実現するために高度な数学的スキルが必要です。
3. サウンドエンジン
Photon Services サウンド エンジン モジュールは、バックグラウンド ミュージック、サウンドエフェクト、オーディオ トラックの切り替え、3 次元サウンド エフェクトなどを含むゲーム サウンドの処理を担当します。Photon Services サウンド エンジンは OpenAL API を使用して実装されており、さまざまなオーディオ形式とコーデックをサポートしています。
手順:
- AudioSource コンポーネントを作成します。
- 音源の種類、距離モデル、効果音のミキシングなどのパラメータを設定します。
- 効果音ファイルのパスを設定します。
- 音響効果パラメータを調整します。
using Photon.Pun;
public class MusicManager : MonoBehaviourPun
{
AudioSource audioSource;
void Awake()
{
audioSource = GetComponent<AudioSource>();
}
[PunRPC]
void PlayMusic(string name)
{
string path = Application.streamingAssetsPath + "/music/" + name;
audioSource.PlayOneShot(Resources.Load<AudioClip>(path));
}
void OnJoinedRoom()
{
photonView.RPC("PlayMusic", RpcTarget.AllBuffered, "background_music");
}
}
上の例は、サウンド エンジンの使用方法を示しています。AudioSource コンポーネントを作成し、音源タイプ、距離モデル、効果音ミキシングなどのパラメータを設定します。AudioSource の効果音ファイルのパスを設定し、PlayOneShot() メソッドを呼び出して効果音を再生します。初期化中に、photonView.RPC() 関数が呼び出され、BGM 情報がすべてのクライアントに送信されます。
サウンド エンジンは、オーディオ ダイナミック レンジ (DOA) 計算、ダイナミック ポジショニング、環境音響効果、音響効果キャプチャ、音響効果編集およびその他の機能を実装するために使用することもできます。
4.フレンドシステム
バディ システムは、Photon Services の高度な機能の 1 つです。ゲームとは独立したソーシャル機能を提供し、プレイヤーが他のプレイヤーとつながり、ゲーム内のデータやリソースを共有できるようにします。
手順:
- 友達リストを作成します。
- 友達リストを取得します。
- 友達の追加、削除、検索。
- 友達のエイリアスを設定します。
using Photon.Pun;
public class FriendManager : MonoBehaviourPun
{
static List<Friend> friendsList = new List<Friend>();
public class Friend
{
public string username;
public string alias;
public Friend(string uName, string aName)
{
username = uName;
alias = aName;
}
}
public bool AddFriend(string username, string alias)
{
foreach (Friend f in friendsList)
{
if (f.username == username)
{
return false; // Already exists
}
}
friendsList.Add(new Friend(username, alias));
return true;
}
public bool RemoveFriend(string username)
{
for (int i = 0; i < friendsList.Count; ++i)
{
if (friendsList[i].username == username)
{
friendsList.RemoveAt(i);
return true;
}
}
return false;
}
public bool FindFriend(string searchText, out List<Friend> foundList)
{
foundList = new List<Friend>();
foreach (Friend f in friendsList)
{
if (f.alias.ToLower().Contains(searchText.ToLower()))
{
foundList.Add(f);
}
}
return foundList.Count > 0;
}
}
上記の例は、フレンド システムの実装を示しています。まず、フレンドリストを保存するための FriendManager クラスの静的変数 friendsList を作成します。Friend クラスは、友人のユーザー名とエイリアスをカプセル化するために使用されます。AddFriend() 関数は新しい友達の追加に使用され、RemoveFriend() 関数は友達の削除に使用され、FindFriend() 関数は友達の検索に使用されます。
クライアントがルームに参加した後、photonView.RPC() 関数を呼び出すことで、フレンド リストをすべてのクライアントに送信できます。他のクライアントは友達リストを読み取り、それに応じて処理できます。たとえば、クライアントは友人のリストを表示し、プレーヤーがチャットする友人を選択できるようにすることができます。
5. グループ制度
グループ化システムは、Photon Services のもう 1 つの高度な機能です。複数のプレーヤーを一元管理する機能を提供し、プレーヤーが組織化して問題を解決するために協力できるようにします。
手順:
- グループを作る;
- グループに参加;
- グループを離れる。
- グループメンバーをクエリします。
using Photon.Pun;
public class GroupManager : MonoBehaviourPun
{
const byte MaxPlayersPerGroup = 4;
Dictionary<byte, List<int>> groups = new Dictionary<byte, List<int>>();
public bool CreateGroup(byte groupId)
{
if (groups.ContainsKey(groupId))
{
return false; // Already exists
}
groups.Add(groupId, new List<int>());
return true;
}
public bool JoinGroup(byte groupId)
{
if (photonView.IsMine && groups.ContainsKey(groupId) && groups[groupId].Count >= MaxPlayersPerGroup)
{
return false; // Full
}
int playerId = photonView.OwnerActorNr;
if (!groups.ContainsKey(groupId))
{
groups.Add(groupId, new List<int>());
}
groups[groupId].Add(playerId);
return true;
}
public bool LeaveGroup(byte groupId)
{
if (!groups.ContainsKey(groupId))
{
return false; // Not in group
}
groups[groupId].Remove(photonView.OwnerActorNr);
return true;
}
public bool QueryGroups()
{
SendGroupsToMaster();
return true;
}
[PunRPC]
void ReceiveGroups(Dictionary<byte, List<int>> receivedGroups)
{
groups = receivedGroups;
}
void SendGroupsToMaster()
{
photonView.Rpc("ReceiveGroups", RpcTarget.MasterClient, groups);
}
}
上記の例は、グループ化システムの実装を示しています。まず、各グループが保持できるプレーヤーの最大数を表す MaxPlayersPerGroup 定数を作成します。次に、各グループのプレーヤーのリストを保存する辞書グループを作成します。
グループの作成 CreateGroup() 関数は、GroupId がすでに存在するかどうかを確認し、存在しない場合は、新しい空のグループを作成します。JoinGroup() 関数は、指定されたグループが存在するかどうか、およびグループ メンバーの数が最大値に達しているかどうかを確認します。参加できる場合は、グループ内のプレーヤーのリストに ActorNumber を追加してください。同様に、LeaveGroup() 関数は、ActorNumber を通じて所属するグループを削除します。QueryGroups() 関数は、グループ情報をホスト クライアントに送信します。
クライアントがルームに参加すると、SendGroupsToMaster() 関数が呼び出され、グループ情報がホスト クライアントに送信されます。ホスト クライアントはグループ情報を受信した後、ReceiveGroups() 関数を呼び出してグループ メンバーを更新します。すべてのクライアントは、QueryGroups() 関数を呼び出してグループ情報を取得できます。
グループ化システムは、ゲーム内容の共有、チームランキング、意思決定の相談などの機能にも使用できます。
6.多言語サポート
Photon Services の多言語サポート モジュールは、さまざまな言語でゲームを提供できます。ゲーム開発者はサーバー側で言語設定を行うだけで、クライアントは自動的に対応する言語に切り替わって表示されます。
手順:
- サーバー言語設定を構成します。
- クライアントは自動的に言語を切り替えます。
using Photon.Pun;
public class LanguageManager : MonoBehaviourPun
{
const byte DefaultLanguageCode = 0x01;
public enum LanguageCodes : byte
{
English = 0x01,
Spanish = 0x02,
French = 0x03,
German = 0x04,
Italian = 0x05
}
byte languageCode;
public bool ChangeLanguage(LanguageCodes langCode)
{
switch (langCode)
{
case LanguageCodes.English:
languageCode = (byte) LanguageCodes.English;
break;
case LanguageCodes.Spanish:
languageCode = (byte) LanguageCodes.Spanish;
break;
case LanguageCodes.French:
languageCode = (byte) LanguageCodes.French;
break;
case LanguageCodes.German:
languageCode = (byte) LanguageCodes.German;
break;
case LanguageCodes.Italian:
languageCode = (byte) LanguageCodes.Italian;
break;
}
SaveSettings();
return true;
}
void LoadSettings()
{
// Read settings from file or database here
languageCode = PlayerPrefs.GetInt("language", DefaultLanguageCode);
}
void SaveSettings()
{
// Write settings to file or database here
PlayerPrefs.SetInt("language", languageCode);
}
void Start()
{
LoadSettings();
photonView.RPC("ChangeLanguageRPC", RpcTarget.All, (LanguageCodes) languageCode);
}
}
上の例は、多言語サポートの実装を示しています。まず、デフォルトの言語エンコーディング DefaultLanguageCode とサポートされる言語エンコーディング LanguageCodes が定義されます。サーバーの言語設定を構成する方法は、言語構成ファイルを変更し、それをすべてのクライアントと同期することです。クライアントが起動すると、LoadSettings() 関数を呼び出して現在の言語設定を読み取り、photonView.RPC() 関数を通じて言語を変更するようにすべてのクライアントに通知します。
クライアント言語設定の切り替え処理は、photonView.RPC()関数内で行われます。サーバーは「言語変更」コマンド (ChangeLanguageRPC) をすべてのクライアントに送信し、コマンドを受信した各クライアントは、LanguageManager オブジェクトの ChangeLanguage() 関数を呼び出して言語設定を更新します。設定を保存する方法はここで実装できます。
Photon Services はテキスト翻訳ツールを提供していませんが、開発者はローカリゼーション開発に同様のツールを使用できます。
7. チャットシステム
チャット システムは、プレイヤーがランダムなテキスト、音声、グラフィック、表現などを通じてゲーム内でコミュニケーションできるようにするリアルタイム コミュニケーション ツールです。
手順:
- チャット ルームを作成します。
- チャットルームに参加してください。
- チャット ルームから退出します。
- メッセージを送ります;
using Photon.Pun;
public class ChatManager : MonoBehaviourPun
{
const int MessageHistoryLength = 100;
public struct ChatMessage
{
public string sender;
public string message;
public ChatMessage(string sName, string msg)
{
sender = sName;
message = msg;
}
}
Dictionary<int, List<ChatMessage>> chatRooms = new Dictionary<int, List<ChatMessage>>();
public bool JoinChatRoom(int roomId)
{
if (chatRooms.ContainsKey(roomId))
{
return false; // Already joined
}
chatRooms.Add(roomId, new List<ChatMessage>());
return true;
}
public bool LeaveChatRoom(int roomId)
{
if (!chatRooms.ContainsKey(roomId))
{
return false; // Not in room
}
chatRooms.Remove(roomId);
return true;
}
public bool SendMessage(int roomId, string message)
{
if (!chatRooms.ContainsKey(roomId))
{
return false; // No such room
}
var history = chatRooms[roomId];
if (history.Count > MessageHistoryLength)
{
history.RemoveRange(0, history.Count - MessageHistoryLength);
}
var msg = new ChatMessage(photonView.Owner.NickName, message);
history.Add(msg);
SendMessageToOtherClients(roomInfo.Id, msg);
return true;
}
void SendMessageToOtherClients(int roomId, ChatMessage message)
{
foreach (KeyValuePair<int, GameObject> kv in PhotonNetwork.CurrentRoom.Players)
{
if (kv.Key!= photonView.owner.ActorNumber)
{
kv.Value.GetPhotonView(this).RPC("ReceiveMessage",
RpcTarget.OthersBufferedViaServer, roomId, message);
}
}
}
[PunRPC]
void ReceiveMessage(int roomId, ChatMessage message)
{
var messages = chatRooms[roomId];
if (messages.Count > MessageHistoryLength)
{
messages.RemoveRange(0, messages.Count - MessageHistoryLength);
}
messages.Add(message);
RenderMessagesInUI(roomId);
}
void RenderMessagesInUI(int roomId)
{
// Show messages in UI here
}
}
上記の例は、チャット システムの実装を示しています。まず、ChatMessage 構造を作成して、チャット メッセージの送信者と内容を保存します。MessageHistoryLength 定数は、メッセージ レコードの最大長を制限するために定義されています。
チャット ルームの作成 JoinChatRoom() 関数は、ルーム番号 roomId を使用してルームに参加しているかどうかを確認します。参加していない場合は、新しいルームを作成し、chatRooms ディクショナリに追加します。チャット ルームから退出するLeaveChatRoom() 関数は、roomId を通じて現在いるルームを削除します。
メッセージの送信 SendMessage() 関数は、roomId を通じて指定されたルームが存在するかどうかを確認し、新しいチャット メッセージをメッセージ履歴リストに追加します。最後に、SendMessageToOtherClients() 関数を呼び出して、他のクライアントにメッセージを送信します。クライアントはメッセージを受信すると、ReceiveMessage() 関数を呼び出してメッセージ履歴リストを更新し、RenderMessagesInUI() 関数を呼び出してメッセージを UI 上に表示します。
チャット システムは、プライベート チャット、チャンネル ディスカッション、ゲーム内チャット ルーム、その他の機能にも使用できます。
8. データストレージ
オンラインゲームにとって、データストレージは非常に重要です。これにより、プレイヤーはセーブ、スコア、設定、個人情報などのゲーム データを保存できます。Photon Services が提供するファイル ストレージ インターフェイスを使用すると、追加料金なしで大容量のファイルを保存できます。
手順:
- ファイルのアップロード;
- ドキュメントをダウンロード;
- ファイルを削除します。
using Photon.Pun;
public class DataManager : MonoBehaviourPun
{
public async Task UploadFile(byte[] data, string fileName)
{
var webRequest = UnityWebRequest.Put($"http://myserver.com/{
fileName}", data);
await webRequest.SendWebRequest();
if (!webRequest.isHttpError &&!webRequest.isNetworkError)
{
Debug.Log("Upload successful!");
}
else
{
Debug.LogError("Upload failed! " + webRequest.error);
}
}
public async Task DownloadFile(string fileName)
{
var webRequest = UnityWebRequest $"http://myserver.com/{
fileName}";
await webRequest.SendWebRequest();
if (!webRequest.isHttpError &&!webRequest.isNetworkError)
{
var downloadedData = webRequest.downloadHandler.data;
ProcessDownloadedData(downloadedData);
}
else
{
Debug.LogError("Download failed! " + webRequest.error);
}
}
public bool DeleteFile(string fileName)
{
var filePath = Path.Combine(Application.persistentDataPath, fileName);
File.Delete(filePath);
return true;
}
}
上の例は、データ ストレージの実装を示しています。ファイル アップロードの UploadFile() 関数は、UnityWebRequest オブジェクトを通じて HTTP PUT リクエストを非同期に送信し、応答を待ちます。ファイルのダウンロード DownloadFile() 関数は、UnityWebRequest オブジェクトを通じて HTTP GET リクエストを非同期に送信し、応答を待ち、応答データを読み取ります。ProcessDownloadedData() 関数は、ダウンロードされたデータを処理するために使用されます。ファイルの削除 DeleteFile() 関数は、ローカル ファイル システムを通じて指定されたファイルを削除します。
ファイル検索、ファイル共有、バージョン管理、権限管理など、ファイル ストレージ インターフェイスを拡張できる領域は数多くあります。