MagicOnion環境構築(macOS+Rider)

環境

  • macOS Catalina 10.15.3

  • Unity 2019.3.5f1

  • Jet Brains Rider 2019.3.4

環境構築

フォルダ作成

全体のプロジェクトを入れるためのフォルダを作成する。

ここではMagicOnionSampleとする

Unityプロジェクト作成

MagicOnionSampleフォルダの中にUnityのプロジェクトを作成する。

プロジェクト名をここではSample.Unityとする。

PlayerSettingsの設定

  • APICompatibilityLevel.NET4.x に変更する(全てのプラットフォームで設定する)

  • Allow unsafe Code にチェックをいれる

  • Scripting Define SymbolsENABLE_UNSAFE_MSGPACK

フォルダ作成

  • MagicOnionSample/Sample.Unity/Assets/Editor
  • MagicOnionSample/Sample.Unity/Assets/Scripts
  • MagicOnionSample/Sample.Unity/Assets/Scripts/Generated
  • MagicOnionSample/Sample.Unity/Assets/Scripts/Shared
  • MagicOnionSample/Sample.Unity/Assets/Plugins
  • GeneratorTools (MagicOnionSampleと同じ階層に作る)

.gitignoreを編集

Pluginsフォルダ以下はGit管理の対象外とする。

.gitignoreに次の文を追加

**/Plugins/

gRPCのダウンロード

https://packages.grpc.io

ここから、最新のビルドをクリックし、C#のカテゴリにある、grpc_unity_packageと名前が付いているものをダウンロード

ダウンロードして解凍したPluginsフォルダの中から、

  • Grpc.Core
  • Grpc.Core.Api

の2つをMagicOnionSample/Sample.Unity/Assets/Plugins/ の中に入れる

MessagePack for CSharp

Releases · neuecc/MessagePack-CSharp · GitHub

から

  • MessagePack.Unity.x.x.xx.unitypackage
  • mpc.zip

の2つをダウンロード

MessagePack.Unity.x.x.xx.unitypackage をインポート

mpcをGeneratorToolsフォルダの中に入れる。

MagicOnionをインストール

Releases · Cysharp/MagicOnion · GitHub

から

  • MagicOnion.Client.Unity.unitypackage
  • moc.zip

の2つをダウンロード。

MagicOnion.Client.Unity.unitypackage をインポート。

moc.zipを解凍し、GeneratorToolsフォルダの中に入れる。

サーバーへ接続するための Script の用意

MagicOnionSample/Sample.Unity/Assets/Scene/ フォルダに次のようなスクリプトを作成する。

using Grpc.Core;
using UnityEngine;

public class SampleController : MonoBehaviour
{
    private Channel channel;

    void Start()
    {
        this.channel = new Channel("localhost:12345", ChannelCredentials.Insecure);
    }

    async void OnDestroy()
    {
        await this.channel.ShutdownAsync();
    }
}

Start() でサーバーへ接続し、OnDestroy() で切断するようになっている。

SampleController という空のゲームオブジェクトを作成し、SampleControllerをアタッチする。

Unity ⇔ サーバー間のコード共有の動作確認用の class の用意

ここからMagicOnionSample/Sample.Unity/Assets/Scripts/Shared以下に置いてあるスクリプトを全て共有する設定を行う。

MagicOnionSample/Sample.Unity/Assets/Scripts/Shared/ に次のようなスクリプトを作成する。

using MessagePack;
using UnityEngine;

namespace Sample.Shared
{
    [MessagePackObject]
    public class Player
    {
        [Key(0)]
        public string Name { get; set; }
        [Key(1)]
        public Vector3 Position { get; set; }
        [Key(2)]
        public Quaternion Rotation { get; set; }
    }
}

サーバー側の設定

サーバー用のソリューションを作成

  Riderで New -> .NET Core -> Console Application

Solution NameとProject NameをSample.Serverに設定

Project DirectoryはMagicOnionSample/ 直下になるようにする。

Put solution and project in the same directoryにチェックを入れる

Create -> New window

MagicOnionをServerにインストール

Rider上でSample.Serverを右クリック -> Manage NuGet Packages

Packagesタブに切り替え、MagicOnionと検索

MagicOnion.Hostingを選択し、Sample.Serverの右の+マークをクリック

MessagePack.UnityShims のインストール

同様にMessagePack.UnityShimsをインストール

クライアントコードを共有する

Sample.Server.csprojを開いて次の文を追加する

(Sample.Server.csprojを開くには、RiderのExplolerでSample.Serverを右クリック -> Edit -> Edit Sample.Server.csproj)

<ItemGroup>
    <Compile Include="..\Sample.Unity\Assets\Scripts\Shared\**\*.cs" LinkBase="Shared" />
</ItemGroup>

Program.csの編集

using Grpc.Core;
using MagicOnion.Hosting;
using MagicOnion.Server;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;

namespace Sample.Server
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // gRPC のログをコンソールに出力するよう設定
            GrpcEnvironment.SetLogger(new Grpc.Core.Logging.ConsoleLogger());

            await MagicOnionHost.CreateDefaultBuilder()
                .UseMagicOnion(
                    new MagicOnionOptions(isReturnExceptionStackTraceInErrorDetail: true), // エラー発生時のメッセージがコンソールに出力されるようにする
                    new ServerPort("localhost", 12345, ServerCredentials.Insecure)) // MagicOnion サーバーが localhost:12345 で Listen する
                .RunConsoleAsync();
        }
    }
}

補足

もし実行時に「開発元を確認できないため、開けません」と表示された時は、

システム環境設定 -> セキュリティとプライバシー -> 一般 -> このまま許可(開く)

とする

(参考 : Macで「開発元を確認できないため、開けません」と表示された時の対処法 | ゴリミー)

コードジェネレート

IL2CPPに対応するためにコードジェネレートする。(IL2CPPは動的なコード生成に対応していないため)

次のようなスクリプトを作成し、Editorフォルダに入れる。

#if UNITY_EDITOR
using System.Diagnostics;
using UnityEditor;
using UnityEngine;

public class MenuItems : MonoBehaviour
{
    [MenuItem("MagicOnion/CodeGenerate")]
    private static void GenerateFormatters()
    {
        // Generate MagicOnion code.
        ExecuteMagicOnionCodeGenerator();

        // Generate MessagePack code.
        ExecuteMessagePackCodeGenerator();
    }
    
    private static void ExecuteMagicOnionCodeGenerator()
    {
        UnityEngine.Debug.Log($"{nameof(ExecuteMagicOnionCodeGenerator)} : start");

        var exProcess = new Process();

        var rootPath = Application.dataPath + "/../..";
        var filePath = rootPath + "/../GeneratorTools/MagicOnion.Generator/bin/moc";
        var exeFileName = "/osx-x64/moc";

        var psi = new ProcessStartInfo()
        {
            CreateNoWindow = true,
            WindowStyle = ProcessWindowStyle.Hidden,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            FileName = filePath + exeFileName,
            Arguments =
                $@"-i ""{rootPath}/MagicOnionApiDemo.Server/MagicOnionApiDemo.Server.csproj"" -o ""{Application.dataPath}/Scripts/Generated/MagicOnion.Generated.cs""",
            // Pathの「Sample.Server」と「Sample.Server.csproj」は実際のものに書き換えてください
        };

        var p = Process.Start(psi);

        p.EnableRaisingEvents = true;
        p.Exited += (object sender, System.EventArgs e) =>
        {
            var data = p.StandardOutput.ReadToEnd();
            UnityEngine.Debug.Log($"{data}");
            UnityEngine.Debug.Log($"{nameof(ExecuteMagicOnionCodeGenerator)} : end");
            p.Dispose();
            p = null;
        };
    }

    private static void ExecuteMessagePackCodeGenerator()
    {
        UnityEngine.Debug.Log($"{nameof(ExecuteMessagePackCodeGenerator)} : start");

        var exProcess = new Process();

        var rootPath = Application.dataPath + "/../..";
        var filePath = rootPath + "/../GeneratorTools/mpc";
        var exeFileName = "/osx/mpc";

        var psi = new ProcessStartInfo()
        {
            CreateNoWindow = true,
            WindowStyle = ProcessWindowStyle.Hidden,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            FileName = filePath + exeFileName,
            Arguments =
                $@"-i ""{Application.dataPath}/../Assembly-CSharp.csproj"" -o ""{Application.dataPath}/Scripts/Generated/MessagePack.Generated.cs""",
        };

        var p = Process.Start(psi);

        p.EnableRaisingEvents = true;
        p.Exited += (object sender, System.EventArgs e) =>
        {
            var data = p.StandardOutput.ReadToEnd();
            UnityEngine.Debug.Log($"{data}");
            UnityEngine.Debug.Log($"{nameof(ExecuteMessagePackCodeGenerator)} : end");
            p.Dispose();
            p = null;
        };
    }
}
#endif

補足

「開発元を確認できないため~~」 と表示された場合

もし実行時に「開発元を確認できないため、開けません」と表示された時は、

システム環境設定 -> セキュリティとプライバシー -> 一般 -> このまま許可(開く)

とする

(参考 : Macで「開発元を確認できないため、開けません」と表示された時の対処法 | ゴリミー)

MessagePack for C#のコードジェネレートで失敗した場合

chmod +x ./GeneratorTools/mpc/osx/mpc

のようにしてmpcに実行権限を追加してやると上手くいくようになる。

InitialSettings スクリプトの作成

次のようなスクリプトを作成し、Assets/Scripts 以下に置いておく。

using MessagePack;
using MessagePack.Resolvers;
using UnityEngine;

namespace Assets.Scripts
{
    class InitialSettings
    {
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        static void RegisterResolvers()
        {
            // NOTE: Currently, CompositeResolver doesn't work on Unity IL2CPP build. Use StaticCompositeResolver instead of it.
            StaticCompositeResolver.Instance.Register(
                MagicOnion.Resolvers.MagicOnionResolver.Instance,
                MessagePack.Resolvers.GeneratedResolver.Instance,
                BuiltinResolver.Instance,
                PrimitiveObjectResolver.Instance,
                MessagePack.Unity.UnityResolver.Instance,
                MessagePack.Unity.Extension.UnityBlitWithPrimitiveArrayResolver.Instance
            );

            MessagePackSerializer.DefaultOptions = MessagePackSerializer.DefaultOptions
                .WithResolver(StaticCompositeResolver.Instance);
        }
    }
}

参考