RakeupGame サンプル (ServerSide) についての解説

目次

  概要
  ClientSide と ServerSide のプログラムの違い
  RakeupGame サンプルの補足


概要

サーバサイドプログラミングの実装事例についての解説

  ここでは、サーバサイドプログラムの作成技法について、実装サンプルを元に解説します。

  解説の大元となるサンプルについては、以下のページで実行方法を説明していますので、先にサンプル内容をご確認ください。

   【クライアントサイドのみで動作しているサンプル】
     ・「■ MUN クライアントサンプル」 > 「RakeupGame サンプル(ClientSide)」

   【ゲームの根幹部分をサーバサイドに移行させているサンプル】
     ・「■ MUN サーバサンプル」 > 「RakeupGame サンプル(ServerSide)」


ClientSide と ServerSide のプログラムの違い

2つのパッケージのコードを比較する

  上記で示している2つのパッケージの違いは何か、について、
  具体的に違うところ(言い換えれば、サーバサイドに移植している箇所)を明示していきます。
まずはクライアントサイドのプログラムの比較です。

Rakeup_ClientSide.unity のソースは、以下の場所にあります。
  client/Assets/Monobit Unity Networking/Samples/Scripts/RakeupGame/ClientSide/RakeupGame.cs

中身を開くと、以下のように記述されています。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MonobitEngine.Sample.ClientSide
{
    /**
     * @brief   アイテム収集ゲームサンプル(クライアントサイド版).
     */
    public class RakeupGame : MonobitEngine.MonoBehaviour
    {
        /**
         * @brief ゲーム内プレイヤーオブジェクト情報.
         */
        public static List<SD_Unitychan_PC> s_PlayerObject = new List<SD_Unitychan_PC>();

        /**
         * @brief ルーム名.
         */
        private string roomName = "";

        /**
         * @brief 制限時間.
         */
        private Int64 gameTimeLimit = 6000;

        /**
         * @brief 1ゲーム中に表示するアイテム数.
         */
        private const Int64 gameItemLimit = 256;

        /**
         * @brief 配置済みアイテム数.
         */
        private Int64 gameItemIsPut = 0;

        /**
         * @brief 全クライアント総計の、取得済みアイテム数.
         */
        private Int64 gameItemRakeupCount = 0;

        /**
         * @brief ゲーム開始フラグ.
         */
        private bool isGameStart = false;

        /**
         * @brief ゲーム終了フラグ.
         */
        private bool isGameEnd = false;

        /**
         * @brief 自身のオブジェクトを生成したかどうかのフラグ.
         */
        private bool isSpawnMyChara = false;

        /**
         * @brief 意図しないタイミングで Disconnect されたかどうかのフラグ.
         */
        private bool isUnsafeDisconnect = false;

        /**
         * @brief 現在途中切断処理中かどうかのフラグ.
         */
        private bool isDisconnecting = false;

        /**
         * @brief 現在途中切断復帰中かどうかのフラグ.
         */
        private bool isReconnecting = false;

        /**
         * @brief 再接続する場合のルーム名.
         */
        private string reconnectRoomName = "";

        /**
         * @brief 再接続する場合の自身のオブジェクト.
         */
        private SD_Unitychan_PC myObject = null;

        /**
         * @brief 再接続する場合の自身のオブジェクトの位置.
         */
        private Vector3 myPosition = new Vector3();

        /**
         * @brief 再接続する場合の自身のオブジェクトの姿勢.
         */
        private Quaternion myRotation = new Quaternion();

        /**
         * @brief アイテム情報.
         */
        public static List<GameObject> itemObject = new List<GameObject>();

        /**
         * @brief   UI表示周り.
         */
        void OnGUI()
        {
            // GUI用の解像度を調整する
            Vector2 guiScreenSize = new Vector2(800, 480);
            if (Screen.width > Screen.height)
            {
                // landscape
                GUIUtility.ScaleAroundPivot(new Vector2(Screen.width / guiScreenSize.x, Screen.height / guiScreenSize.y), Vector2.zero);
            }
            else
            {
                // portrait
                GUIUtility.ScaleAroundPivot(new Vector2(Screen.width / guiScreenSize.y, Screen.height / guiScreenSize.x), Vector2.zero);
            }

            // 縁取りを行なうように、位置をずらしつつ表示
            GUILayout.BeginArea(new Rect(-1, -1, guiScreenSize.x, guiScreenSize.y));
            {
                OnGUI_InWindow(false, new GUIStyleState() { textColor = Color.black });
            }
            GUILayout.EndArea();
            GUILayout.BeginArea(new Rect(-1, 1, guiScreenSize.x, guiScreenSize.y));
            {
                OnGUI_InWindow(false, new GUIStyleState() { textColor = Color.black });
            }
            GUILayout.EndArea();
            GUILayout.BeginArea(new Rect(1, -1, guiScreenSize.x, guiScreenSize.y));
            {
                OnGUI_InWindow(false, new GUIStyleState() { textColor = Color.black });
            }
            GUILayout.EndArea();
            GUILayout.BeginArea(new Rect(1, 1, guiScreenSize.x, guiScreenSize.y));
            {
                OnGUI_InWindow(false, new GUIStyleState() { textColor = Color.black });
            }
            GUILayout.EndArea();
            GUILayout.BeginArea(new Rect(0, 0, guiScreenSize.x, guiScreenSize.y));
            {
                OnGUI_InWindow(true, new GUIStyleState() { textColor = Color.white });
            }
            GUILayout.EndArea();
        }

        /**
         * @brief   更新関数.
         */
        void Update()
        {
            // ゲーム中の処理
            if (isGameStart && !isGameEnd)
            {
                // まだ自分のキャラクタのspawnが終わっていない状態であれば、自身のキャラクタをspawnする
                if (!isSpawnMyChara)
                {
                    OnGameStart();
                }

                // 自身のキャラクタ位置の退避
                if (myObject != null)
                {
                    myPosition = myObject.transform.position;
                    myRotation = myObject.transform.rotation;
                }

                // ルーム名の退避
                if (MonobitNetwork.room != null)
                {
                    reconnectRoomName = MonobitNetwork.room.name;
                }

                // ホストの場合の処理
                if (MonobitNetwork.isHost)
                {
                    // アイテムに接近したら、アイテム取得処理を実行する
                    if (gameItemRakeupCount < gameItemLimit)
                    {
                        for (int index = itemObject.Count - 1; index >= 0; --index)
                        {
                            foreach( SD_Unitychan_PC playerObject in s_PlayerObject)
                            {
                                if ((playerObject.transform.position - itemObject[index].transform.position).magnitude < 1.0f)
                                {
                                    // そのオブジェクトを削除する
                                    MonobitNetwork.Destroy(itemObject[index]);
                                    itemObject.Remove(itemObject[index]);

                                    // 自身のスコア情報を加算するRPC処理を実行する
                                    monobitView.RPC("OnUpdateScore", MonobitTargets.All, playerObject.GetPlayerID(), playerObject.MyScore + 100);
                                    gameItemRakeupCount++;
                                }
                            }
                        }
                    }
                    else
                    {
                        // ゲーム終了メッセージを送信
                        monobitView.RPC("OnGameEnd", MonobitTargets.All, null);
                    }

                    // 制限時間の減少
                    if (gameTimeLimit > 0)
                    {
                        gameTimeLimit--;
                    }
                    else
                    {
                        // ゲーム終了メッセージを送信
                        monobitView.RPC("OnGameEnd", MonobitTargets.All, null);
                    }

                    // 個数制限&時間制限のタイミングで、ゲームシーン上にオブジェクトを配置
                    if ((gameItemIsPut < gameItemLimit) && (gameTimeLimit % 10 == 0))
                    {
                        // ある程度ランダムな位置・姿勢でプレイヤーを配置する
                        Vector3 position = Vector3.zero;
                        position.x = UnityEngine.Random.Range(-10.0f, 10.0f);
                        position.z = UnityEngine.Random.Range(-10.0f, 10.0f);
                        Quaternion rotation = Quaternion.AngleAxis(UnityEngine.Random.Range(-180.0f, 180.0f), Vector3.up);

                        // オブジェクトの配置(他クライアントにも同時にInstantiateする)
                        MonobitNetwork.InstantiateSceneObject("item", position, rotation, 0, null);

                        // ゲームオブジェクト出現個数を加算
                        gameItemIsPut++;
                    }

                    // 制限時間をRPCで送信
                    monobitView.RPC("TickCount", MonobitTargets.Others, gameTimeLimit);
                }
            }
        }

        /**
         * @brief   GUI表示回りの内部処理.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_InWindow(bool isDispInputGUI, GUIStyleState state)
        {
            // 接続済みかどうか
            if (MonobitNetwork.isConnect)
            {
                // 途中切断による再接続処理フラグを設定
                isUnsafeDisconnect = true;

                // 接続情報の表示
                OnGUI_Stats(isDispInputGUI, state);

                // ルーム入室済みかどうか
                if (MonobitNetwork.inRoom)
                {
                    if (isGameStart && !isGameEnd)
                    {
                        OnGUI_InGame(isDispInputGUI, state);
                    }
                    else if (isGameEnd)
                    {
                        OnGUI_Result(isDispInputGUI, state);
                    }
                    OnGUI_InRoom(isDispInputGUI, state);
                }
                else
                {
                    OnGUI_InLobby(isDispInputGUI, state);
                }
            }
            else
            {
                OnGUI_Offline(isDispInputGUI, state);
                // 意図しないタイミングで切断されたとき
                if (isUnsafeDisconnect)
                {
                    GUILayout.Window(0, new Rect(Screen.width / 2 - 100, Screen.height / 2 - 40, 200, 80), OnGUI_UnsafeDisconnect, "Disconnect");
                }
            }
        }

        // ウィンドウ表示
        void OnGUI_UnsafeDisconnect(int WindowID)
        {
            GUIStyle style = new GUIStyle();
            style.alignment = TextAnchor.MiddleCenter;
            GUIStyleState stylestate = new GUIStyleState();
            stylestate.textColor = Color.white;
            style.normal = stylestate;
            if (isDisconnecting)
            {
                GUILayout.Label("MUN is disconnect.\nAre you reconnect?", style);
                GUILayout.BeginHorizontal();
                GUILayout.FlexibleSpace();
                if (GUILayout.Button("Yes", GUILayout.Width(50)))
                {
                    // もう一度接続処理を実行する
                    isDisconnecting = false;
                    isReconnecting = true;
                    isGameStart = false;
                    isGameEnd = false;
                    isSpawnMyChara = false;
                    MonobitNetwork.ConnectServer("RakeupGame_ClientSide_v1.0");
                }
                if (GUILayout.Button("No", GUILayout.Width(50)))
                {
                    isDisconnecting = false;
                }
                GUILayout.FlexibleSpace();
                GUILayout.EndHorizontal();
            }
            else
            {
                GUILayout.Label("reconnecting...", style);
            }
        }

        /**
         * @brief   オフライン中のGUI表示.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_Offline(bool isDispInputGUI, GUIStyleState state)
        {
            // プレイヤー名入力GUIレイヤー
            {
                // Header
                GUILayout.Label("Player Name Entry", new GUIStyle() { normal = state, fontSize = 24 });
                GUILayout.BeginHorizontal();
                GUILayout.Space(25);
                GUILayout.BeginVertical();

                GUILayout.BeginHorizontal();
                {
                    // プレイヤー名の入力欄の表示
                    GUILayout.Label("Input player name:", new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
                    if (isDispInputGUI)
                    {
                        MonobitNetwork.playerName = GUILayout.TextField((MonobitNetwork.playerName == null) ? "" : MonobitNetwork.playerName, GUILayout.Width(150));
                    }

                    // サーバ接続ボタン入力表示
                    if (isDispInputGUI)
                    {
                        if (GUILayout.Button("Connect Server", GUILayout.Width(150)))
                        {
                            // 名前欄が空欄であれば、GUIDで生成したプレイヤー名でサーバに接続
                            if (MonobitNetwork.playerName == null || MonobitNetwork.playerName.Length <= 0)
                            {
                                MonobitNetwork.playerName = Guid.NewGuid().ToString();
                            }

                            // デフォルトロビーへの強制入室をONにする。
                            MonobitNetwork.autoJoinLobby = true;

                            // デバッグログ出力を無効にする
                            MonobitNetwork.LogLevel = MonobitLogLevel.Informational;

                            // まだ未接続の場合、MonobitNetworkに接続する。
                            if (MonobitNetwork.connectionStateDetailed == PeerState.PeerCreated)
                            {
                                isDisconnecting = false;
                                isReconnecting = false;
                                isGameStart = false;
                                isGameEnd = false;
                                isSpawnMyChara = false;
                                MonobitNetwork.ConnectServer("RakeupGame_ClientSide_v1.0");
                            }
                        }
                    }
                }
                GUILayout.EndHorizontal();

                // Footer
                GUILayout.EndVertical();
                GUILayout.EndHorizontal();
            }
        }

        /**
         * @brief   オンライン&ロビー入室中のGUI表示.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_InLobby(bool isDispInputGUI, GUIStyleState state)
        {
            // ルーム作成GUIレイヤー
            {
                // Header
                GUILayout.Label("Create Room/Join Random", new GUIStyle() { normal = state, fontSize = 18 });
                GUILayout.BeginHorizontal();
                GUILayout.Space(25);
                GUILayout.BeginVertical();

                GUILayout.BeginHorizontal();
                {
                    // ルーム名の入力欄、および作成ボタン入力表示
                    GUILayout.Label("Input room name:", new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
                    if (isDispInputGUI)
                    {
                        this.roomName = GUILayout.TextField(this.roomName, GUILayout.Width(200));
                        if (GUILayout.Button("Create Room", GUILayout.Width(150)))
                        {
                            // 入力があった場合、指定された名前で4人部屋を生成
                            MonobitNetwork.CreateRoom(roomName, new RoomSettings() { isVisible = true, isOpen = true, maxPlayers = 4 }, null);
                        }
                    }
                    else
                    {
                        GUILayout.Space(25);
                    }
                }
                GUILayout.EndHorizontal();

                // ルームの作成ボタン入力表示
                if (isDispInputGUI)
                {
                    if (GUILayout.Button("Join Random", GUILayout.Width(150)))
                    {
                        // 入力があった場合、指定された名前で4人部屋を生成
                        MonobitNetwork.CreateRoom(roomName, new RoomSettings() { isVisible = true, isOpen = true, maxPlayers = 4 }, null);
                    }
                }
                else
                {
                    GUILayout.Space(25);
                }

                // Footer
                GUILayout.EndVertical();
                GUILayout.EndHorizontal();
            }

            // ルームリストGUIレイヤー
            {
                // ルーム一覧の取得
                RoomData[] roomList = MonobitNetwork.GetRoomData();

                // ルーム一覧が存在する場合、ルームリストを表示
                if (roomList != null)
                {
                    // Header
                    GUILayout.Label("Created Room List", new GUIStyle() { normal = state, fontSize = 24 });
                    GUILayout.BeginHorizontal();
                    GUILayout.Space(25);
                    GUILayout.BeginVertical();

                    // ルームリストについて、1件ずつボタン入力表示
                    foreach (RoomData room in roomList)
                    {
                        if (isDispInputGUI && room.open && room.visible)
                        {
                            if (GUILayout.Button(room.name + " (" + room.playerCount + "/" + room.maxPlayers + ")", GUILayout.Width(300)))
                            {
                                MonobitNetwork.JoinRoom(room.name);
                            }
                        }
                    }

                    // Footer
                    GUILayout.EndVertical();
                    GUILayout.EndHorizontal();
                }
            }
        }

        /**
         * @brief   オンライン&ルーム入室中のGUI表示.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_InRoom(bool isDispInputGUI, GUIStyleState state)
        {
            // ルーム退室GUIレイヤー
            {
                GUILayout.Space(25);

                // ルーム退室ボタン入力表示
                if (isDispInputGUI)
                {
                    if (GUILayout.Button("Leave Room", GUILayout.Width(150)))
                    {
                        // 安全なDisconnectが実行されていることを成立させる
                        isUnsafeDisconnect = false;

                        // 入力があった場合、ルームから退室する
                        MonobitNetwork.LeaveRoom();
                    }
                }
                else
                {
                    GUILayout.Space(25);
                }

                // ゲーム開始ボタン入力表示
                if (isDispInputGUI && !isGameStart)
                {
                    if (GUILayout.Button("Game Start", GUILayout.Width(150)))
                    {
                        // バトルスタートを通知
                        monobitView.RPC("OnGameStart", MonobitTargets.All, null);
                    }
                }
                else
                {
                    GUILayout.Space(25);
                }
            }
        }

        /**
         * @brief   ゲームプレイ中の表示.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_InGame(bool isDispInputGUI, GUIStyleState state)
        {
            // Header
            GUILayout.Label("Game Info", new GUIStyle() { normal = state, fontSize = 24 });
            GUILayout.BeginHorizontal();
            GUILayout.Space(25);
            GUILayout.BeginVertical();

            // 制限時間の表示
            if (isGameStart && gameTimeLimit > 0)
            {
                // 制限時間の表示
                GUILayout.Label("Rest Frame : " + gameTimeLimit, new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
            }

            // Footer
            GUILayout.EndVertical();
            GUILayout.EndHorizontal();
        }

        /**
         * @brief   ゲームプレイ結果の表示.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_Result(bool isDispInputGUI, GUIStyleState state)
        {
            // Header
            GUILayout.Label("Game Result", new GUIStyle() { normal = state, fontSize = 24 });
            GUILayout.BeginHorizontal();
            GUILayout.Space(25);
            GUILayout.BeginVertical();

            // リザルトの表示
            foreach (SD_Unitychan_PC playerObject in s_PlayerObject)
            {
                GUILayout.Label("Player ID : " + playerObject.GetPlayerID() + " Name : " + playerObject.GetPlayerName() + " Score : " + playerObject.MyScore,
                                new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
            }

            // Footer
            GUILayout.EndVertical();
            GUILayout.EndHorizontal();
        }

        /**
         * @brief   接続情報の表示.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_Stats(bool isDispInputGUI, GUIStyleState state)
        {
            // Header
            GUILayout.Label("Stats Info", new GUIStyle() { normal = state, fontSize = 24 });
            GUILayout.BeginHorizontal();
            GUILayout.Space(25);
            GUILayout.BeginVertical();

            // オンライン上の自身のプレイヤー名の表示
            if (MonobitNetwork.playerName != null)
            {
                GUILayout.Label("My Player Name : " + MonobitNetwork.playerName, new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
            }

            // 現在入室中のルーム情報を表示
            if (MonobitNetwork.inRoom)
            {
                GUILayout.Label("Entry Room : " + MonobitNetwork.room.name + " (" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")", new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
                GUILayout.Label("Host Player : " + MonobitNetwork.host.name, new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
                GUILayout.Label("Room Status : " + ((MonobitNetwork.room.visible) ? "Visible, " : "Invisible, ") + ((MonobitNetwork.room.open) ? "Open" : "Closed"), new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
            }

            // Footer
            GUILayout.EndVertical();
            GUILayout.EndHorizontal();
        }

        /**
         * @brief   接続が切断されたときの処理.
         */
        public void OnDisconnectedFromServer()
        {
            Debug.Log("Disconnected from Monobit");
            isDisconnecting = true;
        }

        /**
         * @brief   接続失敗時の処理.
         * @param   parameters  接続失敗時の詳細情報.
         */
        public void OnConnectToServerFailed(object parameters)
        {
            Debug.Log("OnConnectToServerFailed : StatusCode = " + parameters + ", ServerAddress = " + MonobitNetwork.ServerAddress);
            isDisconnecting = true;
        }

        /**
         * @brief   接続が確立した時の処理.
         */
        public void OnJoinedLobby()
        {
            // 現在残っているルーム情報から再接続を実行する
            if (!string.IsNullOrEmpty(reconnectRoomName))
            {
                MonobitNetwork.JoinRoom(reconnectRoomName);
            }
        }

        /**
         * @brief   指定ルーム入室失敗時の処理.
         * @param   parameters  入室失敗時の詳細情報.
         */
        public void OnJoinRoomFailed(object[] parameters)
        {
            Debug.Log("OnJoinRoomFailed : ErrorCode = " + parameters[0] + ", DebugMsg = " + parameters[1]);
        }

        /**
         * @brief   ゲームスタートを受信(RPC).
         */
        [MunRPC]
        void OnGameStart()
        {
            // ゲームスタートフラグを立てる
            isGameStart = true;

            // 変数初期化
            gameTimeLimit = 6000;
            gameItemIsPut = 0;
            gameItemRakeupCount = 0;

            if (!isReconnecting)
            {
                // ある程度ランダムな位置・姿勢でプレイヤーを配置する
                myPosition = Vector3.zero;
                myPosition.x = UnityEngine.Random.Range(-10.0f, 10.0f);
                myPosition.z = UnityEngine.Random.Range(-10.0f, 10.0f);
                myRotation = Quaternion.AngleAxis(UnityEngine.Random.Range(-180.0f, 180.0f), Vector3.up);
            }

            // プレイヤーの配置(他クライアントにも同時にInstantiateする)
            GameObject go = MonobitNetwork.Instantiate("SD_unitychan_PC", myPosition, myRotation, 0);
            myObject = go.GetComponent<SD_Unitychan_PC>();

            // 出現させたことを確認
            isSpawnMyChara = true;

            // 再接続処理完了
            isReconnecting = false;
        }

        /**
         * @brief   ゲーム終了を受信(RPC).
         */
        [MunRPC]
        void OnGameEnd()
        {
            // ゲーム終了フラグを立てる
            isGameEnd = true;
        }

        /**
         * @brief   制限時間を受信(RPC).
         * @param   timeLimit   受信した制限時間.
         */
        [MunRPC]
        void TickCount(Int64 timeLimit)
        {
            // ゲームスタートフラグを立てる(途中参加者のための処置)
            isGameStart = true;

            // 制限時間を同期する
            this.gameTimeLimit = timeLimit;
        }

        /**
         * @brief   スコアの更新(RPC).
         * @param   playerId    該当するプレイヤーID.
         * @param   score       更新するスコアの値.
         */
        [MunRPC]
        void OnUpdateScore(Int32 playerId, UInt64 score)
        {
            foreach (SD_Unitychan_PC playerObject in s_PlayerObject)
            {
                if (playerObject.GetPlayerID() == playerId)
                {
                    playerObject.MyScore = score;
                }
            }
        }
    }
}

一方、Rakeup_ServerSide.unity のソースは、以下の場所にあります。
  client/Assets/Monobit Unity Networking/Samples/Scripts/RakeupGame/ServerSide/RakeupGame.cs

中身を開くと、以下のように記述されています。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MonobitEngine.Sample.ServerSide
{
    /**
     * @brief   アイテム収集ゲームサンプル(サーバサイド版).
     */
    public class RakeupGame : MonobitEngine.MonoBehaviour
    {
        /**
         * @brief ゲーム内プレイヤーオブジェクト情報.
         */
        public static List<SD_Unitychan_PC> s_PlayerObject = new List<SD_Unitychan_PC>();

        /**
         * @brief ルーム名.
         */
        private string roomName = "";

        /**
         * @brief 制限時間.
         */
        private Int64 gameTimeLimit = 0;

        /**
         * @brief ゲーム開始フラグ.
         */
        private bool isGameStart = false;

        /**
         * @brief ゲーム終了フラグ.
         */
        private bool isGameEnd = false;

        /**
         * @brief 意図しないタイミングで Disconnect されたかどうかのフラグ.
         */
        private bool isUnsafeDisconnect = false;

        /**
         * @brief 現在途中切断処理中かどうかのフラグ.
         */
        private bool isDisconnecting = false;

        /**
         * @brief 現在途中切断復帰中かどうかのフラグ.
         */
        private bool isReconnecting = false;

        /**
         * @brief 再接続する場合のルーム名.
         */
        private string reconnectRoomName = "";

        /**
         * @brief 再接続する場合の自身のオブジェクト.
         */
        private GameObject myObject = null;

        /**
         * @brief 再接続する場合の自身のオブジェクトの位置.
         */
        private Vector3 myPosition = new Vector3();

        /**
         * @brief 再接続する場合の自身のオブジェクトの姿勢.
         */
        private Quaternion myRotation = new Quaternion();

        /**
         * @brief アイテム情報.
         */
        public static List<GameObject> itemObject = new List<GameObject>();

        /**
         * @brief   UI表示周り.
         */
        void OnGUI()
        {
            // GUI用の解像度を調整する
            Vector2 guiScreenSize = new Vector2(800, 480);
            if (Screen.width > Screen.height)
            {
                // landscape
                GUIUtility.ScaleAroundPivot(new Vector2(Screen.width / guiScreenSize.x, Screen.height / guiScreenSize.y), Vector2.zero);
            }
            else
            {
                // portrait
                GUIUtility.ScaleAroundPivot(new Vector2(Screen.width / guiScreenSize.y, Screen.height / guiScreenSize.x), Vector2.zero);
            }

            // 縁取りを行なうように、位置をずらしつつ表示
            GUILayout.BeginArea(new Rect(-1, -1, guiScreenSize.x, guiScreenSize.y));
            {
                OnGUI_InWindow(false, new GUIStyleState() { textColor = Color.black });
            }
            GUILayout.EndArea();
            GUILayout.BeginArea(new Rect(-1, 1, guiScreenSize.x, guiScreenSize.y));
            {
                OnGUI_InWindow(false, new GUIStyleState() { textColor = Color.black });
            }
            GUILayout.EndArea();
            GUILayout.BeginArea(new Rect(1, -1, guiScreenSize.x, guiScreenSize.y));
            {
                OnGUI_InWindow(false, new GUIStyleState() { textColor = Color.black });
            }
            GUILayout.EndArea();
            GUILayout.BeginArea(new Rect(1, 1, guiScreenSize.x, guiScreenSize.y));
            {
                OnGUI_InWindow(false, new GUIStyleState() { textColor = Color.black });
            }
            GUILayout.EndArea();
            GUILayout.BeginArea(new Rect(0, 0, guiScreenSize.x, guiScreenSize.y));
            {
                OnGUI_InWindow(true, new GUIStyleState() { textColor = Color.white });
            }
            GUILayout.EndArea();
        }

        /**
         * @brief   更新関数.
         */
        void Update()
        {
            // ゲームスタート時の処理
            if (isGameStart)
            {
                // 自身のキャラクタ位置の退避
                if (myObject != null)
                {
                    myPosition = myObject.transform.position;
                    myRotation = myObject.transform.rotation;
                }

                // ルーム名の退避
                if (MonobitNetwork.room != null)
                {
                    reconnectRoomName = MonobitNetwork.room.name;
                }
            }
        }

        /**
         * @brief   GUI表示回りの内部処理.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_InWindow(bool isDispInputGUI, GUIStyleState state)
        {
            // 接続済みかどうか
            if (MonobitNetwork.isConnect)
            {
                // 途中切断による再接続処理フラグを設定
                isUnsafeDisconnect = true;

                // 接続情報の表示
                OnGUI_Stats(isDispInputGUI, state);

                // ルーム入室済みかどうか
                if (MonobitNetwork.inRoom)
                {
                    if (isGameStart && !isGameEnd)
                    {
                        OnGUI_InGame(isDispInputGUI, state);
                    }
                    else if (isGameEnd)
                    {
                        OnGUI_Result(isDispInputGUI, state);
                    }
                    OnGUI_InRoom(isDispInputGUI, state);
                }
                else
                {
                    OnGUI_InLobby(isDispInputGUI, state);
                }
            }
            else
            {
                OnGUI_Offline(isDispInputGUI, state);
                // 意図しないタイミングで切断されたとき
                if (isUnsafeDisconnect)
                {
                    GUILayout.Window(0, new Rect(Screen.width / 2 - 100, Screen.height / 2 - 40, 200, 80), OnGUI_UnsafeDisconnect, "Disconnect");
                }
            }
        }

        // ウィンドウ表示
        void OnGUI_UnsafeDisconnect(int WindowID)
        {
            GUIStyle style = new GUIStyle();
            style.alignment = TextAnchor.MiddleCenter;
            GUIStyleState stylestate = new GUIStyleState();
            stylestate.textColor = Color.white;
            style.normal = stylestate;
            if (isDisconnecting)
            {
                GUILayout.Label("MUN is disconnect.\nAre you reconnect?", style);
                GUILayout.BeginHorizontal();
                GUILayout.FlexibleSpace();
                if (GUILayout.Button("Yes", GUILayout.Width(50)))
                {
                    // もう一度接続処理を実行する
                    isDisconnecting = false;
                    isReconnecting = true;
                    isGameStart = false;
                    isGameEnd = false;
                    MonobitNetwork.ConnectServer("RakeupGame_ServerSide_v1.0");
                }
                if (GUILayout.Button("No", GUILayout.Width(50)))
                {
                    isDisconnecting = false;

                    // シーンをオフラインシーンへ
                    MonobitNetwork.LoadLevel(OfflineSceneReconnect.SceneNameOffline);
                }
                GUILayout.FlexibleSpace();
                GUILayout.EndHorizontal();
            }
            else
            {
                GUILayout.Label("reconnecting...", style);
            }
        }

        /**
         * @brief   オフライン中のGUI表示.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_Offline(bool isDispInputGUI, GUIStyleState state)
        {
            // プレイヤー名入力GUIレイヤー
            {
                // Header
                GUILayout.Label("Player Name Entry", new GUIStyle() { normal = state, fontSize = 24 });
                GUILayout.BeginHorizontal();
                GUILayout.Space(25);
                GUILayout.BeginVertical();

                GUILayout.BeginHorizontal();
                {
                    // プレイヤー名の入力欄の表示
                    GUILayout.Label("Input player name:", new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
                    if (isDispInputGUI)
                    {
                        MonobitNetwork.playerName = GUILayout.TextField((MonobitNetwork.playerName == null) ? "" : MonobitNetwork.playerName, GUILayout.Width(150));
                    }

                    // サーバ接続ボタン入力表示
                    if (isDispInputGUI)
                    {
                        if (GUILayout.Button("Connect Server", GUILayout.Width(150)))
                        {
                            // 名前欄が空欄であれば、GUIDで生成したプレイヤー名でサーバに接続
                            if (MonobitNetwork.playerName == null || MonobitNetwork.playerName.Length <= 0)
                            {
                                MonobitNetwork.playerName = Guid.NewGuid().ToString();
                            }

                            // デフォルトロビーへの強制入室をONにする。
                            MonobitNetwork.autoJoinLobby = true;

                            // デバッグログ出力を無効にする
                            MonobitNetwork.LogLevel = MonobitLogLevel.Informational;

                            // まだ未接続の場合、MonobitNetworkに接続する。
                            if (MonobitNetwork.connectionStateDetailed == PeerState.PeerCreated)
                            {
                                isDisconnecting = false;
                                isReconnecting = false;
                                isGameStart = false;
                                isGameEnd = false;
                                MonobitNetwork.ConnectServer("RakeupGame_ServerSide_v1.0");
                            }
                        }
                    }
                }
                GUILayout.EndHorizontal();

                // Footer
                GUILayout.EndVertical();
                GUILayout.EndHorizontal();
            }
        }

        /**
         * @brief   オンライン&ロビー入室中のGUI表示.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_InLobby(bool isDispInputGUI, GUIStyleState state)
        {
            // ルーム作成GUIレイヤー
            {
                // Header
                GUILayout.Label("Create Room/Join Random", new GUIStyle() { normal = state, fontSize = 18 });
                GUILayout.BeginHorizontal();
                GUILayout.Space(25);
                GUILayout.BeginVertical();

                GUILayout.BeginHorizontal();
                {
                    // ルーム名の入力欄、および作成ボタン入力表示
                    GUILayout.Label("Input room name:", new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
                    if (isDispInputGUI)
                    {
                        this.roomName = GUILayout.TextField(this.roomName, GUILayout.Width(200));
                        if (GUILayout.Button("Create Room", GUILayout.Width(150)))
                        {
                            // 入力があった場合、指定された名前で4人部屋を生成
                            MonobitNetwork.CreateRoom(roomName, new RoomSettings() { isVisible = true, isOpen = true, maxPlayers = 4 }, null);
                        }
                    }
                    else
                    {
                        GUILayout.Space(25);
                    }
                }
                GUILayout.EndHorizontal();

                // ルームの作成ボタン入力表示
                if (isDispInputGUI)
                {
                    if (GUILayout.Button("Join Random", GUILayout.Width(150)))
                    {
                        // 入力があった場合、指定された名前で4人部屋を生成
                        MonobitNetwork.CreateRoom(roomName, new RoomSettings() { isVisible = true, isOpen = true, maxPlayers = 4 }, null);
                    }
                }
                else
                {
                    GUILayout.Space(25);
                }

                // Footer
                GUILayout.EndVertical();
                GUILayout.EndHorizontal();
            }

            // ルームリストGUIレイヤー
            {
                // ルーム一覧の取得
                RoomData[] roomList = MonobitNetwork.GetRoomData();

                // ルーム一覧が存在する場合、ルームリストを表示
                if (roomList != null)
                {
                    // Header
                    GUILayout.Label("Created Room List", new GUIStyle() { normal = state, fontSize = 24 });
                    GUILayout.BeginHorizontal();
                    GUILayout.Space(25);
                    GUILayout.BeginVertical();

                    // ルームリストについて、1件ずつボタン入力表示
                    foreach (RoomData room in roomList)
                    {
                        if (isDispInputGUI && room.open && room.visible)
                        {
                            if (GUILayout.Button(room.name + " (" + room.playerCount + "/" + room.maxPlayers + ")", GUILayout.Width(300)))
                            {
                                MonobitNetwork.JoinRoom(room.name);
                            }
                        }
                    }

                    // Footer
                    GUILayout.EndVertical();
                    GUILayout.EndHorizontal();
                }
            }
        }

        /**
         * @brief   オンライン&ルーム入室中のGUI表示.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_InRoom(bool isDispInputGUI, GUIStyleState state)
        {
            // ルーム退室GUIレイヤー
            {
                GUILayout.Space(25);

                // ゲーム開始までの時間表示
                if(!isGameStart && gameTimeLimit > 0)
                {
                    GUILayout.Label("Rest Time To Game Start : " + gameTimeLimit, new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
                }

                // ルーム退室ボタン入力表示
                if (isDispInputGUI)
                {
                    if (GUILayout.Button("Leave Room", GUILayout.Width(150)))
                    {
                        // 安全なDisconnectが実行されていることを成立させる
                        isUnsafeDisconnect = false;

                        // 入力があった場合、ルームから退室する
                        MonobitNetwork.LeaveRoom();
                    }
                }
                else
                {
                    GUILayout.Space(25);
                }
            }
        }

        /**
         * @brief   ゲームプレイ中の表示.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_InGame(bool isDispInputGUI, GUIStyleState state)
        {
            // Header
            GUILayout.Label("Game Info", new GUIStyle() { normal = state, fontSize = 24 });
            GUILayout.BeginHorizontal();
            GUILayout.Space(25);
            GUILayout.BeginVertical();

            // 制限時間の表示
            if (isGameStart && gameTimeLimit > 0)
            {
                GUILayout.Label("Rest Frame : " + gameTimeLimit, new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
            }

            // Footer
            GUILayout.EndVertical();
            GUILayout.EndHorizontal();
        }

        /**
         * @brief   ゲームプレイ結果の表示.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_Result(bool isDispInputGUI, GUIStyleState state)
        {
            // Header
            GUILayout.Label("Game Result", new GUIStyle() { normal = state, fontSize = 24 });
            GUILayout.BeginHorizontal();
            GUILayout.Space(25);
            GUILayout.BeginVertical();

            // リザルトの表示
            foreach (SD_Unitychan_PC playerObject in s_PlayerObject)
            {
                GUILayout.Label("Player ID : " + playerObject.GetPlayerID() + " Name : " + playerObject.GetPlayerName() + " Score : " + playerObject.MyScore,
                                new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
            }

            // Footer
            GUILayout.EndVertical();
            GUILayout.EndHorizontal();
        }

        /**
         * @brief   接続情報の表示.
         * @param   isDispInputGUI  入力系GUIを表示するかどうかのフラグ(縁取り文字描画機能を活かした場合の、多重描画を防止するため).
         * @param   state           文字表示色.
         */
        private void OnGUI_Stats(bool isDispInputGUI, GUIStyleState state)
        {
            // Header
            GUILayout.Label("Stats Info", new GUIStyle() { normal = state, fontSize = 24 });
            GUILayout.BeginHorizontal();
            GUILayout.Space(25);
            GUILayout.BeginVertical();

            // オンライン上の自身のプレイヤー名の表示
            if (MonobitNetwork.playerName != null)
            {
                GUILayout.Label("My Player Name : " + MonobitNetwork.playerName, new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
            }

            // 現在入室中のルーム情報を表示
            if (MonobitNetwork.inRoom)
            {
                GUILayout.Label("Entry Room : " + MonobitNetwork.room.name + " (" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")", new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
                GUILayout.Label("Host Player : " + MonobitNetwork.host.name, new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
                GUILayout.Label("Room Status : " + ((MonobitNetwork.room.visible) ? "Visible, " : "Invisible, ") + ((MonobitNetwork.room.open) ? "Open" : "Closed"), new GUIStyle() { normal = state, fontSize = 16, margin = new RectOffset(6, 6, 6, 6), fixedWidth = 140 });
            }

            // Footer
            GUILayout.EndVertical();
            GUILayout.EndHorizontal();
        }

        /**
         * @brief   接続が切断されたときの処理.
         */
        public void OnDisconnectedFromServer()
        {
            Debug.Log("Disconnected from Monobit");
            isDisconnecting = true;
        }

        /**
         * @brief   接続失敗時の処理.
         * @param   parameters  接続失敗時の詳細情報.
         */
        public void OnConnectToServerFailed(object parameters)
        {
            Debug.Log("OnConnectToServerFailed : StatusCode = " + parameters + ", ServerAddress = " + MonobitNetwork.ServerAddress);
            isDisconnecting = true;
        }

        /**
         * @brief   接続が確立した時の処理.
         */
        public void OnJoinedLobby()
        {
            // 現在残っているルーム情報から再接続を実行する
            if( !string.IsNullOrEmpty(reconnectRoomName) )
            {
                MonobitNetwork.JoinRoom(reconnectRoomName);
            }
        }

        /**
         * @brief   指定ルーム入室失敗時の処理.
         * @param   parameters  入室失敗時の詳細情報.
         */
        public void OnJoinRoomFailed(object[] parameters)
        {
            Debug.Log("OnJoinRoomFailed : ErrorCode = " + parameters[0] + ", DebugMsg = " + parameters[1]);
        }

        /**
         * @brief   ゲームスタートを受信(RPC).
         */
        [MunRPC]
        void OnPrepareToGameStart(Int64 time)
        {
            gameTimeLimit = time;
        }

        /**
         * @brief   ゲームスタートを受信(RPC).
         */
        [MunRPC]
        void OnGameStart()
        {
            // ゲームスタートフラグを立てる
            isGameStart = true;

            if (!isReconnecting)
            {
                // ある程度ランダムな位置・姿勢でプレイヤーを配置する
                myPosition = Vector3.zero;
                myPosition.x = UnityEngine.Random.Range(-10.0f, 10.0f);
                myPosition.z = UnityEngine.Random.Range(-10.0f, 10.0f);
                myRotation = Quaternion.AngleAxis(UnityEngine.Random.Range(-180.0f, 180.0f), Vector3.up);
            }

            // プレイヤーの配置(他クライアントにも同時にInstantiateする)
            myObject = MonobitNetwork.Instantiate("SD_unitychan_PC", myPosition, myRotation, 0);

            // 再接続処理完了
            isReconnecting = false;
        }

        /**
         * @brief   制限時間を受信(RPC).
         * @param   timeLimit   受信した制限時間.
         */
        [MunRPC]
        void OnTickCount(Int64 timeLimit)
        {
            // 制限時間を同期する
            gameTimeLimit = timeLimit;
        }

        /**
         * @brief   スコアの更新(RPC).
         * @param   playerId    該当するプレイヤーID.
         * @param   score       更新するスコアの値.
         */
        [MunRPC]
        void OnUpdateScore(Int32 playerId, UInt64 score)
        {
            foreach (SD_Unitychan_PC playerObject in s_PlayerObject)
            {
                if(playerObject.GetPlayerID() == playerId )
                {
                    playerObject.MyScore = score;
                }
            }
        }

        /**
         * @brief   ゲームスタートを受信(RPC).
         */
        [MunRPC]
        void OnGameEnd()
        {
            isGameEnd = true;
        }
    }
}

中身を比較すると、いくつか異なる点がありますが、最も大きな違いがあるのが、
RakeupGame.Update() メソッドです。

サーバサイド版の RakeupGame.Update() メソッドは、クライアントサイド版の RakeupGame.Update() メソッドから、
以下の赤枠の部分が取り除かれています。 (赤枠に囲まれていない部分が、サーバサイドで残っているコードです。)

これにより、クライアントサイドから、以下の処理権限が奪われており、
これについて『不正なユーザーがどんなにチート行為などを頑張ったとしても』、ゲームに反映させることができません。
  ・ ゲームスタート処理の実行権限
  ・ アイテム取得判定の権限
  ・ スコア加算の権限
  ・ 制限時間減算の権限
  ・ ゲーム終了処理の実行権限
  ・ アイテムの配置権限
サーバサイドを比較してみます。

Rakeup_ClientSide.unity では、サーバサイドプログラムとして、規定の MUN サーバの他には存在しません。
一方、Rakeup_ServerSide では、サーバサイドプログラムとして、規定の MUN サーバの他に
以下のソースファイルが追加されています。
  server/csharp/mun_room_rakeupgame_serverside/src/database/Custom/MunRoomServerSide.cs

中身を開くと、以下のように記述されています。
using System;
using System.Collections.Generic;

namespace mun_room
{
    /**
     * @brief   サーバサイドプログラムで管理されるクラス.
     */
    class MunRoomServerSide : MonobitEngine.MonoBehaviour
    {
        /** アイテムオブジェクト情報. */
        private class ItemObjectInfo
        {
            public int viewId;
            public MonobitEngine.UnityEngine_Vector3 position;
        }

        /** アイテムオブジェクト情報. */
        private List<ItemObjectInfo> m_ItemObjectInfo = new List<ItemObjectInfo>();

        /** ゲーム開始フラグ. */
        private bool isGameStart = false;

        /** ゲーム終了フラグ. */
        private bool isGameEnd = false;

        /** 前フレームが処理された段階の時刻. */
        private Int64 preparedTime = 0;

        /** 制限時間. */
        private Int64 gameTimeLimit = 0;

        /**1ゲーム中に表示するアイテム数. */
        private const Int64 gameItemLimit = 256;

        /** 配置済みアイテム数. */
        private Int64 gameItemIsPut = 0;

        /** 全クライアント総計の、取得済みアイテム数. */
        private Int64 gameItemRakeupCount = 0;

        /**
         * @brief   作成されたルーム情報.
         */
        MunRoomInfo m_RoomInfo = null;

        /**
         * @brief   MUN クライアントからのコールバック.
         * @param   name    文字列情報.
         * @param   value   浮動小数情報.
         */
        public void MunRoomRpcCallback(string name, float value)
        {
            // TODO : 任意の処理
        }

        /**
         * @brief   コンストラクタ.
         * @param   roomInfo    作成されたルーム情報.
         */
        public MunRoomServerSide(MunRoomInfo roomInfo)
        {
            // 自身のクラスを MonobitNetwork の監視下に登録する
            MonobitEngine.MonobitNetwork.AddBehaviour(this);

            // ルーム情報を退避
            m_RoomInfo = roomInfo;
        }

        /**
         * @brief   デストラクタ.
         */
        ~MunRoomServerSide()
        {
            // 自身のクラスを MonobitNetwork の監視下から除外する
            MonobitEngine.MonobitNetwork.RemoveBehaviour(this);
        }

        /**
         * @brief   ルーム作成時の処理.
         */
        public override void Start()
        {
            // 作成した時間を設定
            mrs.Time nowTime = mrs.Time.Now();
            preparedTime = (Int64)(nowTime.GetSec() * 1000) + (nowTime.GetUsec() / 1000);
            gameTimeLimit = 15000;
            MonobitEngine.MonobitNetwork.RPC(m_RoomInfo.GetId(), 1, "OnPrepareToGameStart", gameTimeLimit);
        }

        /**
         * @brief   ルーム更新時の処理.
         */
        public override void Update()
        {
            // 現在の時刻を設定
            mrs.Time nowTime = mrs.Time.Now();
            Int64 currentTime = (Int64)(nowTime.GetSec() * 1000) + (nowTime.GetUsec() / 1000);

            if (!isGameStart)
            {
                // ゲーム開始までのカウントダウン処理
                gameTimeLimit -= (currentTime - preparedTime);
                preparedTime = currentTime;
                MonobitEngine.MonobitNetwork.RPC(m_RoomInfo.GetId(), 1, "OnPrepareToGameStart", gameTimeLimit);

                // 15秒経過したら、GameStart を伝える
                if (gameTimeLimit <= 0)
                {
                    MonobitEngine.MonobitNetwork.RPC(m_RoomInfo.GetId(), 1, "OnGameStart", null);
                    isGameStart = true;
                    gameTimeLimit = 60000;
                }
            }
            else if (!isGameEnd)
            {
                // 制限時間の減少
                gameTimeLimit -= (currentTime - preparedTime);
                preparedTime = currentTime;
                MonobitEngine.MonobitNetwork.RPC(m_RoomInfo.GetId(), 1, "OnTickCount", gameTimeLimit);

                // 制限時間のタイミングで、ゲームシーン上にオブジェクトを配置
                if ((gameItemIsPut < gameItemLimit) && (gameTimeLimit % 100 == 0))
                {
                    // ある程度ランダムな位置・姿勢でプレイヤーを配置する
                    MonobitEngine.UnityEngine_Vector3 position = new MonobitEngine.UnityEngine_Vector3();
                    Random random = new Random();
                    position.x = (float)(-10.0 + random.NextDouble() * 20.0);
                    position.y = 0.0f;
                    position.z = (float)(-10.0 + random.NextDouble() * 20.0);

                    MonobitEngine.UnityEngine_Quaternion rotation = new MonobitEngine.UnityEngine_Quaternion();
                    float radian = (float)((-180.0 + random.NextDouble() * 360.0) / Math.PI);
                    rotation.x = 0;
                    rotation.y = (float)Math.Sin(radian * 0.5);
                    rotation.z = 0;
                    rotation.w = (float)Math.Cos(radian * 0.5);

                    // オブジェクトの配置(他クライアントにも同時にInstantiateする)
                    MonobitEngine.MonobitNetwork.InstantiateSceneObject(m_RoomInfo.GetId(), "item", position, rotation);

                    // ゲームオブジェクト出現個数を加算
                    gameItemIsPut++;
                }

                // 60秒経過したら、GameEnd を伝え、リザルト表示へ
                if (gameTimeLimit <= 0)
                {
                    MonobitEngine.MonobitNetwork.RPC(m_RoomInfo.GetId(), 1, "OnGameEnd", null);
                    isGameEnd = true;
                }
            }
            else
            {
                // 結果通知中
            }
        }

        /**
         * @brief   MUN サーバからリクエスト送信されたシーンオブジェクトが MUN クライアント上に生成された時のコールバック.
         * @param   prefabName      生成されたシーンオブジェクトのプレハブ名.
         * @param   position        生成されたシーンオブジェクトの位置.
         * @param   rotation        生成されたシーンオブジェクトの向き.
         * @param   viewId          生成されたシーンオブジェクトのMonobitViewID.
         */
        public void OnMonobitInstantiateSceneObject(string prefabName, MonobitEngine.UnityEngine_Vector3 position, MonobitEngine.UnityEngine_Quaternion rotation, Int32 viewId)
        {
            // アイテムオブジェクトがシーンオブジェクトとして生成されたら、その viewID と position を退避
            if (prefabName == "item")
            {
                m_ItemObjectInfo.Add(new ItemObjectInfo() { position = position, viewId = viewId });
            }
        }

        /**
         * @brief   MUN クライアントから オブジェクト同期データが送信されてきた時のコールバック.
         * @param   playerId        送信者であるプレイヤーの ID.
         * @param   viewId          送信オブジェクトが保持する MonobitView の ID.
         * @param   objectName      送信オブジェクトのGameObjectの名前.
         * @param   transData       MonobitTransformView コンポーネントから送信されてきたデータ群.
         * @param   animData        MonobitAnimatorView コンポーネントから送信されてきたデータ群.
         * @param   otherData       MonobitTransformView コンポーネント、および MonobitAnimatorView コンポーネント以外から送信されてきたデータ群。
         *                          このデータで送信されてくるデータには、Transform, Rigidbody, および OnMonobitSerializeView() メソッドで送られるストリーム情報がある.
         */
        public void OnMonobitSerializeView(Int32 playerId, Int32 viewId, string objectName, object[] transData, object[] animData, object[] otherData)
        {
            // ゲーム中かどうかの判別
            if (!isGameStart || isGameEnd)
            {
                return;
            }

            // プレイヤーキャラクタかどうかの判別
            if (playerId > 0)
            {
                // 該当するルーム内プレイヤー情報を検索
                MunRoomPlayerInfo playerInfo = null;
                foreach (MunRoomPlayerInfo player in m_RoomInfo.GetPlayersInRoom())
                {
                    if (player.GetId() == playerId)
                    {
                        playerInfo = player;
                        break;
                    }
                }
                if (playerInfo == null)
                {
                    return;
                }

                if (gameItemRakeupCount < gameItemLimit)
                {
                    // クライアントのプレイヤーキャラクタの MonobitTransformView では、最初に「位置情報」が送信されてくるので、
                    // transData の 0 番目の要素について「プレイヤーキャラクタの座標」として取得する。
                    MonobitEngine.UnityEngine_Vector3 position = (MonobitEngine.UnityEngine_Vector3)transData[0];

                    // アイテム取得判定
                    for (Int32 index = m_ItemObjectInfo.Count - 1; index >= 0; --index)
                    {
                        // 一定距離以内にいる、と判断された場合に取得したことにする
                        float lenX = m_ItemObjectInfo[index].position.x - position.x;
                        float lenZ = m_ItemObjectInfo[index].position.z - position.z;
                        if (lenX * lenX + lenZ * lenZ <= 1.0f)
                        {
                            // スコアの加算
                            playerInfo.AddScore(100);
                            MonobitEngine.MonobitNetwork.RPC(m_RoomInfo.GetId(), 1, "OnUpdateScore", playerId, playerInfo.GetScore());

                            // アイテムプレハブの削除
                            MonobitEngine.MonobitNetwork.DestroySceneObject(m_RoomInfo.GetId(), m_ItemObjectInfo[index].viewId);
                            m_ItemObjectInfo.Remove(m_ItemObjectInfo[index]);

                            // アイテム取得数の加算
                            gameItemRakeupCount++;
                        }
                    }
                }
                else
                {
                    // リザルト表示へ
                    MonobitEngine.MonobitNetwork.RPC(m_RoomInfo.GetId(), 1, "OnGameEnd", null);
                    isGameEnd = true;
                }
            }
        }
    }
}
先に示した「クライアントサイド版の RakeupGame.Update() メソッドから削除された項目について
  【MunRoomServerSide.Update() メソッドに移植された項目】
    ・ ゲームスタート処理の実行権限
    ・ 制限時間減算の権限
    ・ アイテムの配置権限
    ・ ゲーム終了処理の実行権限
  【MunRoomServerSide.OnMonobitSerializeView() メソッドに移植された項目】
    ・ アイテム取得判定の権限
    ・ スコア加算の権限
が移植されています。

 この状態で、上記項目に対して不正行為を起こそうとする場合、サーバクラックの方法以外に手段がなく、
 それは実質(サーバに侵入して該当プロセスを変更しない限り)不可能です。

   ※ 「サーバクラックされたら」という部分については、MUN のシステム構築とはまた別の話になりますので
     ここでの説明は割愛します。



RakeupGame サンプルの補足

クライアントサイド版よりも「よりセキュア面で強固な」サービスの提供は可能ですが…

  クライアントサイド版とサーバサイド版を比較して、チートなどがされにくい、より強固なサービスとして提供することは可能ですが、
  これでもまだ「チートの抜け道」は存在します。

  例えば、今回のサーバサイド版サンプルでは、プレイヤーキャラクタの座標値に対してのチート判定は行なっていません。
  「位置をアイテム出現位置に合わせて瞬間移動させる」系のチートには弱い状態です。

  これについては方法論がいくつかあり、
1. MunRoomServerSide.OnMonobitSerializeView() メソッドで取得した各々のキャラクタの座標値について
  「ある一定以上の距離が離れている(=瞬間移動している)」と判断される場合には、チート行為をしているクライアントと判断して kick する方法。

2. そもそも「キャラクタの移動」についてもクライアント側で処理させるのではなく、クライアント側から入力されたキー情報を送信して
  サーバ側で、そのキー情報に合わせた移動処理を実現する方法。
  などの対応策があります。

  ただし、今回のサンプルでは、以下の理由により採用を見送っています。ご了承ください。
1. については、RakeupGame サンプルの「アイテムの距離」自体がそもそも近いので、距離での判別はあまり意味を為しません。
  → この対応をするチート対策をするならば、前提となるゲームシステム自体の改変が必要です。

2. については、対応手段として最も効果的だが、MunRoomServerSide.OnMonobitSerializeView() メソッドの使い道を
  説明するに至らないので、苦肉の策として「抜け道を残したまま」にしています。
  → この部分については、サーバサイドプログラムを利用される方への「宿題」とします。
       興味があれば、是非 2. について対策を施してみてください。