😸

Mac + RiderでMagicOnion v3でマルチ対応する

2021/06/25に公開

前書き

MagicOnionの使用方法を解説している記事は大体がWindows + VisualStuido環境。自分のメイン環境はMac + Riderなので、同じ様に環境構築したつもりでも色々とつまずくポイントがあって苦労しました。そんな自分のための備忘録です。

サーバーサイドプロジェクト作成

以下のように設定してプロジェクトを作成。

  • slnとcsprojを同じディレクトリに配置するように設定
  • .NETのバージョンは5.0

プロジェクトが作成されたら、NuGetから以下をインストール。

  • MagicOnion.Hosting v3.0.12

サーバー、クライアントで共有するクラスライブラリを作成

以下のように設定してClass Libraryを作成。

  • slnとcsprojを同じディレクトリに配置するように設定
  • .NETのバージョンは5.0 !!!!要検討 mpcを使用する場合はv3にする??

プロジェクトが作成されたら、NuGetから以下をインストール。

  • MagicOnion.Abstractions v3.0.12
  • MessagePack.UnityShim (最新版)

自動で作成されるClass1.csは不要なので削除。

共有設定

Test.Shared

共有用プロジェクトで、Test.Sharedcsprojを開いて、クライアントの共有コードへの相対パスを追加する。

<ItemGroup>
    <Compile Include="../Test.Client/Assets/Scripts/ServerShared/**/*.cs" />
</ItemGroup>

Test.Server

共有用のプロジェクトを追加する

Test.Serverを開いて、ソリューションウィンドウで右クリック、Add > Add Existing Project...で、Test.Shared.csprojを選択する。

参照を追加する

続いてTest.ServerのソリューションウィンドウでTest.Server.csproj上で右クリック、Add > Add Referenceで、Test.Shared.csprojを選択する。

接続の準備

1.Unityからサーバーに接続する

UnityでScripts/MyAppにTestController.csを作成。以下のように編集する。

TestController
namespace MyApp
{
    using Grpc.Core;
    using UnityEngine;

    public class TestController : MonoBehaviour
    {
        private Channel channel;

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

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

作成したらシーン上の適当なゲームオブジェクトにアタッチしておく。

2.サーバー側でMagic Onionを起動させる

Test.Serverでプロジェクト作成時に自動生成されているProgram.csを以下のように編集する。

Program
using MagicOnion.Hosting;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;

namespace Test.Server
{
    class Program
    {
        static async Task Main(string[] args)
        {
            await MagicOnionHost.CreateDefaultBuilder()
                .UseMagicOnion()
                .RunConsoleAsync();
        }
    }
}

ここまで完了した状態で、サーバー側をRun Test.Server、 Unityで実行すると一応動く。

マルチ実装

サーバー、クライアント間でやりとりされるオブジェクトを定義

サーバー、クライアント間でやり取りするデータはMessage Packとして定義する。

Player
using MessagePack;

namespace ServerShared.MessagePackObjects
{
    [MessagePackObject]
    public class Player
    {
        [Key(0)]
        public string Name { get; set; }
    }
}

サーバー、クライアント間のAPIを定義する

  • Client -> ServerのAPI:ITestHub.cs
  • Server -> ClientのITestHubReceiver.cs
    を定義して以下のように編集する。
ITestHub
using System.Threading.Tasks;
using MagicOnion;
using ServerShared.MessagePackObjects;

namespace ServerShared.Hubs
{
    public interface ITestHub : IStreamingHub<ITestHub, ITestHubReceiver>
    {
        /// <summary>
        /// ゲームに接続することをサーバに伝える
        /// </summary>
        Task JoinAsync(Player player);
        /// <summary>
        /// ゲームから切断することをサーバに伝える
        /// </summary>
        Task LeaveAsync();
    }
}
ITestHubReceiver
namespace ServerShared.Hubs
{
    public interface ITestHubReceiver
    {
        /// <summary>
        /// ゲームに接続したことをクライアントに伝える
        /// </summary>
        void OnJoin(string name);
        /// <summary>
        /// ゲームから退出したことをクライアントに伝える
        /// </summary>
        void OnLeave(string name);
    }
}

サーバー側でAPIを実装する

TestHub
using System;
using System.Threading.Tasks;
using MagicOnion.Server.Hubs;
using ServerShared.Hubs;
using ServerShared.MessagePackObjects;

namespace Test.Server.Hub
{
    public class TestHub : StreamingHubBase<ITestHub, ITestHubReceiver>, ITestHub
    {
        private IGroup room;
        private Player self;
        
        public async Task JoinAsync(Player player)
        {
            // とりあえずルームは全ユーザーで固定
            const string roomName = "TestRoom";
            // ルームに参加して保持する 
            room = await this.Group.AddAsync(roomName);
            // 自身の情報を保持
            self = player;
            // 参加を同一ルームの全メンバーに通知
            Broadcast(room).OnJoin(self.Name);
        }

        public async Task LeaveAsync()
        {
            // ルーム内のメンバーから自分を削除
            await room.RemoveAsync(Context);
            // 退出を同一ルームの全メンバーに通知
            Broadcast(room).OnLeave(self.Name);
        }
        
        protected override ValueTask OnConnecting()
        {
            // handle connection if needed.
            Console.WriteLine($"client connected {this.Context.ContextId}");
            return CompletedTask;
        }

        protected override ValueTask OnDisconnected()
        {
            // handle disconnection if needed.
            // on disconnecting, if automatically removed this connection from group.
            return CompletedTask;
        }
    }
}

Unityで実装

すでに追加済みのTestController.csでITestHubReceiverを実装する

TestController
using MagicOnion.Client;
using ServerShared.Hubs;
using ServerShared.MessagePackObjects;

namespace MyApp
{
    using Grpc.Core;
    using UnityEngine;

    public class TestController : MonoBehaviour, ITestHubReceiver
    {
        private Channel channel;
        private ITestHub testHub;

        void Start()
        {
            channel = new Channel("localhost:12345", ChannelCredentials.Insecure);
            testHub = StreamingHubClient.Connect<ITestHub, ITestHubReceiver>(channel, this);
            
            TestHubTest();
        }

        async void OnDestroy()
        {
            await testHub.DisposeAsync();
            await channel.ShutdownAsync();
        }
        
        async void TestHubTest()
        {
            // 自分のプレイヤー情報を作ってみる
            var player = new Player
            {
                Name = "MyName",
            };

            // ゲームに接続する
            await testHub.JoinAsync(player);
        }

        #region サーバーから呼び出されるメソッド
        
        public void OnJoin(string name)
        {
            Debug.Log($"{name} joined.");
        }

        public void OnLeave(string name)
        {
            Debug.Log($"{name} left.");
        }
        
        #endregion
    }
}

サーバー、クライアントを実行すると、コンソールにMyName joined.と表示される。

Mac向けにビルド

マルチ対応を確認するためにはMac向けにビルドする必要がある。この際、ArchitectureがIntel 64-bit + Apple Siliconになっている場合は、Intel 64-bitに設定する。

トラブルシューティング

~~は、開発元を検証できないため開けません。

一旦ウィンドウでキャンセルを選択。システム環境設定 > セキュリティとプライバシーより、画面下部に「このまま許可」を選択する。

Plugin 'grpc_csharp_ext.bundle' is used from several locations:

Mac向けにビルドする際に発生。

Plugin 'grpc_csharp_ext.bundle' is used from several locations:
 Assets/Plugins/Grpc.Core/runtimes/osx/x64/grpc_csharp_ext.bundle would be copied to <PluginPath>/grpc_csharp_ext.bundle
 Assets/Plugins/Grpc.Core/runtimes/osx/x86/grpc_csharp_ext.bundle would be copied to <PluginPath>/grpc_csharp_ext.bundle
Please fix plugin settings and try again.

grpc_csharp_ext.bundleが64bit,32bit環境用に用意されている。32bitは不要なので、x86の方をフォルダごと削除する。

Discussion