はじめに
最近ゲームが面白すぎて時間取れない...
とはいえひと段落したので気になってたMagicOnionに関する記事をアウトプットかねて書いていきます。
トライブナインサ終かなしい...
サーバーについて
サーバーとは、簡単に言えばUnityの外部で処理を担当する仕組みのことです。
一般的なゲームでは、ログイン認証やデータベースへの情報の保存・取得といった用途でよく使われています。
サーバーサイドの実装にはPHPが使われることもありますが、今回はC#を使ってサーバーを構築していきます。
MagicOnionについて
詳細は公式ドキュメント(https://cysharp.github.io/MagicOnion/ja/ )にまとまっていますが、簡単に説明すると、C#を使ってgRPCベースのサーバーを構築できるフレームワークです。
UnityとC#の親和性を活かしながら、リアルタイム通信やRPC(Remote Procedure Call)の実装をシンプルに行うことができます。
提供されているサンプルやテンプレートに沿って進めれば、基本的なサーバー構成はすぐに実装できるようになっています。
実装方法
1.導入
2.処理の流れ
3.実装
という手順で行っていきます。
※サンプルコードにある「MagicOnionFolder」は作成したフォルダ名に依存するのでつけたフォルダ名が違うなら合わせてください。
※この記事を読むのには多少のC#知識は必要です。
導入
※動作はVSCodeで行い、コマンドはターミナルで実行するものとします。
下記コマンドを実行します。
git clone https://github.com/Cysharp/MagicOnion.Template.Unity.git
cd MagicOnion.Template.Unity
.\init.cmd (ProjectName)
そうすると
/user/MagicOnion.Template.Unity/src
にProjectNameで設定したファイルが表示されているかと思います。
下記画像はMagicOnionFolderと設定し行ったものです。
今回はその上の階層のMagicOnion.Template.Unityをデスクトップ上のフォルダにコピーして作業を行っていきます。
Server編
作業を行いやすいようMagicOnion.Template.Unityのフォルダを開いておきましょう。
cd ./src/(ProjectName).Server
続いて、以下のコマンドを実行してサーバーを起動します:
dotnet run
正常に起動すると、以下のようなログが表示されます(ポート番号やパスは環境によって異なる場合があります):
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5244
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path:
ここまで完了していればサーバーの一旦の動作確認は完了です。
Unity編
次にUnityの環境を整えていきます。
MagicOnionは 6000.0.36f1で動作するのでなければインストールを行っておきましょう。
その後ディスクから動作させている環境の
src/(ProjectName).Unity
をUnityで開きます。
この(ProjectName).UnityがクライアントのUnityを作業する内容です。
一旦正常に開けるかは確認しておきましょう。
適当にオブジェクトを配置して、SampleSceneをアタッチします。
そうして実行すると...
エラーが出ず、ログが出力されていれば正常です
ではこれからは自前で処理を作成できるように内容の確認と実装方法を記述していきます。
処理の流れ
まずは理解を深めるためにこれがどのような流れで先ほどの動きになっていたのかを確認しておきましょう。
using System;
using MagicOnion;
using MagicOnion.Client;
using MagicOnionFolder.Shared;
using UnityEngine;
public class SampleScene : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
async void Start()
{
try
{
var channel = GrpcChannelx.ForAddress("http://localhost:5244");
var client = MagicOnionClient.Create<IMyFirstService>(channel);
var result = await client.SumAsync(100, 200);
Debug.Log($"100 + 200 = {result}");
}
catch (Exception e)
{
Debug.LogException(e);
}
}
// Update is called once per frame
void Update()
{
}
}
SampleSceneはこのような内容になっており、先ほどデフォルトでサーバーを起動した際にhttp://localhost:5244 で起動していたのを確認できたかと思いますが、ここにアクセスを行い、IMyFirstServiceというインターフェースを通じてその関数を実行する
という内容になっています。
ではこのIMyFirstServiceとはなんなのでしょうか?
IMyFirstServiceは(ProjectName).Sharedフォルダにあります。
内容を確認しましょう。
using MagicOnion;
namespace MagicOnionFolder.Shared
{
public interface IMyFirstService : IService<IMyFirstService>
{
UnaryResult<int> SumAsync(int x, int y);
}
}
内容はこのようになっていますね。
先ほどSampleSceneにあったSumAsyncが定義されているインターフェースがIMyFirstServiceなのです。
ただし、インターフェースを定義しただけでは動作せず、実際のサービスクラスの実装が必要です。
ではこれの実装先を見ていきましょう。
こちらの内容も確認してみると
using MagicOnion;
using MagicOnion.Server;
using MagicOnionFolder.Shared;
namespace MagicOnionFolder.Server.Services;
// Implements RPC service in the server project.
// The implementation class must inherit `ServiceBase<IMyFirstService>` and `IMyFirstService`
public class MyFirstService : ServiceBase<IMyFirstService>, IMyFirstService
{
// `UnaryResult<T>` allows the method to be treated as `async` method.
public async UnaryResult<int> SumAsync(int x, int y)
{
Console.WriteLine($"Received:{x}, {y}");
return x + y;
}
}
というようになっています。
内容はいかにも簡単であり、xとyというint値を受け取り、その合計値を返す処理が記述されています。
つまり全体の内容を振り返ると...
1.Unityでインターフェースを呼び出し
2.Sharedにあるインターフェースを参照し
3.それを実装しているサービスクラスの内容が実装される
という流れになっていますね。
つまりこの流れを作成すれば自前でサーバー処理を作成できるわけです。
では作成を行いましょう。
実装
まずはサービスクラスを実装していきます。
MagicOnion.Template.Unity\src(ProjectName).Shared
にIGetStringService.csを実装します。
using MagicOnion;
namespace MagicOnionFolder.Shared
{
public interface IGetStringService : IService<IGetStringService>
{
UnaryResult<string> StringAsync(int num);
}
}
int値を受け取りStringを返却するサービスを提供することとしました。
そしてこのインターフェースを利用したサービスクラスを作成していきます。
MagicOnion.Template.Unity\src(ProjectName).Server\Services
にGetStringService.csを実装します。
using MagicOnion;
using MagicOnion.Server;
using MagicOnionFolder.Shared;
namespace MagicOnionFolder.Server.Services;
public class GetStringService : ServiceBase<IGetStringService>, IGetStringService
{
public async UnaryResult<string> StringAsync(int num)
{
switch (num)
{
case 0: return "Hello";
case 1: return "World";
default: return "Other";
}
}
}
0が渡されたら「Hello」、1が渡されたら「World」、それ以外なら「Other」を返すようにするサービスクラスです。
またサービスクラスを正しく実装できているかは
dotnet build
を行いましょう。
また
dotnet run
を再度行いましょう。
正常動作されていればUnityのクライアント側の実装を行います。
簡単なので先ほど利用したSampleScene.csを書き換えます。
using System;
using MagicOnion;
using MagicOnion.Client;
using MagicOnionFolder.Shared;
using UnityEngine;
public class SampleScene : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
async void Start()
{
try
{
var channel = GrpcChannelx.ForAddress("http://localhost:5244");
var client = MagicOnionClient.Create<IGetStringService>(channel);
var result = await client.StringAsync(0);
Debug.Log(result);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
// Update is called once per frame
void Update()
{
}
}
1.Clientに使用するインターフェースをIGetStringServiceに変更
2.await clientの先をStringAsyncにします。
そして起動してHelloが表示されることを確認すれば完了です。
使用したい方法があればこれにのっとりながら作成しましょう。
最後に
簡単でしたがこれでMagicOnionの実装の解説を終わります。
結構簡単に使えるなというのが所感でした。
Unityを利用する上ではサーバーの選択肢には入りそうですしかなり面白いですね。
気が向いたらDBに紐づけるものも投稿します。