▲前のページへ
次のページへ▼

キャラクタ同期(2) RPC通信制御の実装


【目次】
実装前の心得
バトル詳細情報の定数定義
バトル詳細情報の送受信メソッド定義
スタブに展開
Battleサーバの送受信処理
クライアントの送受信処理
バトル終了後にキャラクタを削除する


実装前の心得

実質ここからがモノビットエンジンによる実装になります。
モノビットエンジンはRPCによる通信同期をサポートしており、基本的な作業の流れとしては
以下のような手順になります。
1. どういった情報をサーバーとやり取りするか考える。
2. やり取りする情報を構造体として定義する。
3. やり取りするための関数(メソッド)を定義する。
4. 受信側の関数(メソッド)の内部処理を実装する。
5. 送信側の関数(メソッド)の呼び出しを実装する。

1.についてですが、先にも触れているように、今回やり取りする情報は『キャラクタの姿勢』です。
『キャラクタの姿勢』をデータで表すには、Unityの場合以下の情報が必要となります。
・キャラクタの座標値。Vector3型 = float型×3。
・キャラクタの回転量。Quartanion型 = float型×4。
・キャラクタのアニメーションの種類。今回の場合 Animator Controller で使用しているので Int型。
これに加え、クライアント端末上で複数人のリアルタイム同期を実現するためには、以下の情報が欠かせません。
・キャラクタの固有ID。どのプレイヤーがどのキャラクタを操作しているのかを監視するための情報。
ちなみにこの『キャラクタの固有ID』の情報は既に prefork_battle_matching のシステムに組み込み済みですので
それをそのまま利用しましょう。

2, 3, 4, 5 については後述します。

バトル詳細情報の定数定義

■ battle.rb に定数定義を追加する
  モノビットエンジンのRPC通信はRubyスタブコードによって定数定義や関数定義が出来るようになっています。
  battle.rb を開き、その編集を行っていきましょう。
   ※ battle.rb は prefork_battle_matching_lite/tools/rpc 内に存在しています。

  開いたら、以下の定数定義を 65行目付近 に挿入します。
	# バトル詳細情報定数定義
	structure( :BattlePlayerInfo ) do
		member( :chara_id, :uint64 )		# キャラクタID
		member( :position, :float32, 3 )	# 座標値
		member( :rotation, :float32, 4 )	# 回転量
		member( :animId, :int32 )			# アニメーションID
	end
  

  上述のとおり、
・キャラクタの固有ID。どのプレイヤーがどのキャラクタを操作しているのかを監視するための情報。UInt64型。
・キャラクタの座標値。Vector3型 = float型×3。
・キャラクタの回転量。Quartanion型 = float型×4。
・キャラクタのアニメーションの種類。今回の場合 Animator Controller で使用しているので Int型。
  が必要になりますので、それを構造体 BattlePlayerInfo に追加します。

バトル詳細情報の送受信メソッド定義

■ battle.rb に送受信メソッドを追加する
  続けて、上記 BattlePlayerInfo を送受信する関数(メソッド)の定義を追加します。
  後述になりますが、この送受信はほぼチャット送受信と似た内容になります。

  まずは battle.rb にて「:Chat」で検索して探してください(上記の定数定義を追加した段階で 150行目付近にあると思います)。
  その記述の上側に、以下のメソッド定義を挿入してください。
		function( :c2s, :PlayerInfo ) do
			argument( :playerInfo, :BattlePlayerInfo )
			retfunc( :playerInfo, :BattlePlayerInfo )
		end
  

  BattlePlayerInfo の情報を通じて、クライアント→サーバ および サーバ→クライアントの送受信を実装します。
  送受信しあっている情報は文字列チャットと異なりますが、イメージとしては似た内容になります。
・あるクライアントからサーバに対し、自身が制御しているキャラクタ姿勢情報を送信する。
・サーバは受信したら、送信してきたクライアント以外の全クライアントに対し、キャラクタ姿勢情報を送信する。
・クライアントはサーバから受信したら、それを「非プレイヤーキャラクタ」として姿勢制御を反映させる。
  これだけで実は今回の導入編は組み込めます。


スタブに展開

■ RPCスタブコードを生成する
  battle.rbを編集し終えたら、同フォルダ内に含まれている convert.bat をダブルクリックしスタブコードを生成してください。
  

  実行し終えたら、outフォルダにRPCコードが生成されます。
  これを copy_file.bat を実行することで、server および client にコピーします。
  


Battleサーバの送受信処理

■ Battleサーバの送受信処理を記述する
  サーバプログラムは C++ ですので、まずはメソッド宣言から入ります。
  prefork_battle_matching_lite/server/battle/src フォルダを開き、その中の RPC_BTL_Battle.hpp の 51行目あたりに
  以下のメソッドの宣言を行ってください。
        virtual void Recv_PlayerInfo( uint64 conid, BTL::BattlePlayerInfo &playerInfo );
  

  続けて、同フォルダにある RPC_BTL_Battle.cpp の 127行目あたりに、メソッドの実装を追加します。
void RPC_BTL_Battle::Recv_PlayerInfo( uint64 conid, BTL::BattlePlayerInfo &playerInfo )
{
        for ( CharaInfoMap::iterator itr = m_CharaInfoMap.begin(); m_CharaInfoMap.end() != itr; ++itr ){
                CharaInfo* pInfo = &(itr->second);

                // 自分以外の全員に通知
                if( pInfo->rpcId != conid )
                {
                        DNALOG_DEBUG( "Send_PlayerInfoResult CharaId=0x%llx RpcId=0x%llx ClientId=0x%llx",
                                pInfo->charaId, pInfo->rpcId, pInfo->clientId );
                        GetInterface_Battle( pInfo->rpcId ).Send_PlayerInfoResult( playerInfo );
                }
        }
}
  

  先の説明にもあるとおり、
・サーバは受信したら、送信してきたクライアント以外の全クライアントに対し、キャラクタ姿勢情報を送信する。
  ように作り上げます。既にチャットでは
・サーバは受信したら、全クライアントに対し、文字列情報を送信する。
  を組み上げていますので、これを「送信してきたクライアント以外」「キャラクタ姿勢情報」を変更することで
  受信処理の中身が組み込まれることになります。


■ RoomHubクライアントの受信処理を記述する(ダミー)
  一方こちらはダミーコードになりますが、RoomHubクライアントにも受信処理を組み込みます。
  prefork_battle_matching_lite/server/battle/src フォルダを開き、RPC_BTL_RoomHub.hpp の 64行目付近に
  以下の宣言を追加してください。
        virtual void Recv_PlayerInfoResult( uint64 conid, BTL::BattlePlayerInfo &player_info );
  

  続けて、同フォルダにある RPC_BTL_RoomHub.cpp の 258行目あたりに、メソッドの実装を追加します。
void RPC_BTL_RoomHub::Recv_PlayerInfoResult( uint64 conid, BTL::BattlePlayerInfo &player_info )
{
        DNALOG_DEBUG_D( "Recv_PlayerInfoResult" );
}
  


クライアントの送受信処理

■ クライアントの受信処理を組み込む
  Unity に戻り、クライアント側の送受信処理を組み込みましょう。
  まずは受信処理からです。
  Unity の Project ビューから Assets/Projects/Scripts/sample/pu/ フォルダを開き、その中の GameRpcClientPU.cs の
  175行目付近に追加しましょう。
    public void Recv_PlayerInfoResult(UInt64 conid, BattlePlayerInfo playerInfo)
    {
        lock (ClientScene.g_mutex)
        {
            SD_Unitychan_NPC script;

            // サーバから送られてきた新しいユーザーのキャラクタの場合
            if (!ClientScene.g_CharaList.ContainsKey(playerInfo.chara_id))
            {
                // 新規NPCを出現させる
                ClientScene.g_CharaList[playerInfo.chara_id] = MonoBehaviour.Instantiate(ClientScene.g_unityChan_NPC) as GameObject;
                ClientScene.g_CharaList[playerInfo.chara_id].name = ClientScene.g_unityChan_NPC.name + "_" + mln.Utility.ToHex(playerInfo.chara_id);

                // キャラクタIDの代入
                script = ClientScene.g_CharaList[playerInfo.chara_id].GetComponent<SD_Unitychan_NPC>();
                script.SetCharaID(playerInfo.chara_id);
            }

            // 座標を補間して代入
            Vector3 pos = new Vector3(playerInfo.position[0], playerInfo.position[1], playerInfo.position[2]);
            ClientScene.g_CharaList[playerInfo.chara_id].transform.position += pos;
            ClientScene.g_CharaList[playerInfo.chara_id].transform.position *= 0.5f;

            // 回転を補間して代入
            Quaternion rot = new Quaternion(playerInfo.rotation[0], playerInfo.rotation[1], playerInfo.rotation[2], playerInfo.rotation[3]);
            ClientScene.g_CharaList[playerInfo.chara_id].transform.rotation = Quaternion.Slerp(ClientScene.g_CharaList[playerInfo.chara_id].transform.rotation, rot, 0.5f);

            // アニメーションIDを代入
            script = ClientScene.g_CharaList[playerInfo.chara_id].GetComponent<SD_Unitychan_NPC>();
            script.SetAnimId(playerInfo.animId);
        }
    }
  
  前章にて説明したとおり新しく出現するキャラクタは Instantiate で出現させます。
  出現させた非プレイヤーオブジェクトに対し、任意の座標・回転・アニメーションIDを追加することで
  動かせないオブジェクトを外部から動かせるようになります。(=キャラクタ同期になります)

  なお通信同期にラグが発生することもありえますので、座標や回転量にはある程度の補間を入れて
  数フレーム程度の遅延ではラグを感じさせないようにしておきます。

■ クライアントの送信処理を組み込む
  続けて受信処理です。GameRpcClientPU.cs の 89行目に戻り、以下のコードを追加しておきます。
    public void SendPlayerInfo(BattlePlayerInfo playerInfo) {
        GetInterface_Battle(m_RpcConnector.GetRPCID()).Send_PlayerInfo(playerInfo);
    }
  
  送信処理本体は上述のようになりますが、この playerInfo パラメータに「自身が動かしている
  プレイヤーキャラクタの情報」を与える導線が必要になります。

  Projectビューの Assets/Projects/Scripts/sample/core フォルダを開き、Client.cs の 478行目に
  以下のコードを追加し、導線を完成させます。
    public void Send_BattlePlayerInfo(UInt64 charaId)
    {
        lock (ClientScene.g_mutex)
        {
            // まだプレイヤーキャラクタを登場させていない
            if (!ClientScene.g_CharaList.ContainsKey(charaId))
            {
                // 新しくキャラクタを登場させる。
                ClientScene.g_CharaList[charaId] = MonoBehaviour.Instantiate(ClientScene.g_unityChan_PC) as GameObject;
                ClientScene.g_CharaList[charaId].name = ClientScene.g_unityChan_PC.name + "_" + mln.Utility.ToHex(charaId);

                // キャラクタIDとメインカメラを設定
                SD_Unitychan_PC script = ClientScene.g_CharaList[charaId].GetComponent<SD_Unitychan_PC>();
                script.SetCharaID(charaId);
                script.SetMainCamera(ClientScene.g_CharaList[charaId].transform.FindChild("Camera").camera);
            }

            // 送信情報をセット
            BTL.BattlePlayerInfo playerInfo = new BTL.BattlePlayerInfo();
            playerInfo.chara_id = charaId;
            playerInfo.position[0] = ClientScene.g_CharaList[charaId].transform.position.x;
            playerInfo.position[1] = ClientScene.g_CharaList[charaId].transform.position.y;
            playerInfo.position[2] = ClientScene.g_CharaList[charaId].transform.position.z;
            playerInfo.position_len = 3;
            playerInfo.rotation[0] = ClientScene.g_CharaList[charaId].transform.rotation.x;
            playerInfo.rotation[1] = ClientScene.g_CharaList[charaId].transform.rotation.y;
            playerInfo.rotation[2] = ClientScene.g_CharaList[charaId].transform.rotation.z;
            playerInfo.rotation[3] = ClientScene.g_CharaList[charaId].transform.rotation.w;
            playerInfo.rotation_len = 4;
            playerInfo.animId = ClientScene.g_CharaList[charaId].GetComponent<Animator>().GetInteger("animId");

            // 送信処理へ
            m_pPU.SendPlayerInfo(playerInfo);
        }
    }


  送信処理を行うために必要となる各種データを charaId を使って1つずつ入手し、送信処理を呼び出します。
  Unity のデータ型と RPC のデータ型で異なりますので、注意してください。
  (Vector3型やQuaternion型はfloat型に分解する必要があります。)

  この Client.SendBattlePlayerInfo を「バトル中」に呼び出せば送信実装が可能になります。
  Projectビューの Assets/Projects/Scripts/sample を開き、その中の ClientScene.cs の 69行目に   以下のコードを追加してください。
        UInt64 charaId = Convert.ToUInt64(m_CharaId, 16);
        m_Client.Send_BattlePlayerInfo(charaId);
  

  これで送受信の完成です。


▲前のページへ
次のページへ▼