iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 6
0
自我挑戰組

用Unity製作連線遊戲系列 第 6

Connect with gRPC

昨天跟著教學試了Mirror的方式進行Lobby(大廳)的功能,也就是配對的功能,雖然是統一的方式利用Mirror現行的機制,但思考了一陣子後發覺這樣的方式其實是很侷限的,如果什麼樣的機制都要考量到如何利用Mirror的架構下實現,其實做起來反而會綁手綁腳的。就好比配對來說,雙端都用Mirror,這表示在server端也必需寫在Unity裡,若是配對需要用到某些服務,又或是C#裡沒有好的配對Library,而Python或是Java的環境裡剛好有相當好用的配對Libarary,或許這在樣的情況下,利用Microservice的架構可能會比較洽當。

目前的想法是利用Mirror的部份會是所謂的Game server,也就是實際遊戲所用到的server,利用都是C#的特性甚至是同一個連線框架,來簡化開發和實際在執行時可能會碰到的問題。但非實際遊戲的部份,像是現有的Lobby,則會考慮用其它方式處理,而那個其它方式就是利用gRPC做為連接方式而進行的。

gRPC已經是相當成熟的技術,在Microservice的架構下,現今是主流的service之間溝通的方式。而用在Client端這雖然web的支援還不算是很好,但是無論是手機或是Standalone平台支援度都很高。不過,在Unity裡使用gRPC到還不算是太廣泛的運用,而它最大的問題就是上iOS平台會有不確定的問題,所以多數手機遊戲還沒有採用這個方式。不過UniRx的開發者,在GitHub上有弄一個MagicOnion的專案,就是利用gRPC和一些調整過的功能,讓上手機平台變得可行。

今天的重點不是MagicOnion,而是單純的gRPC。要用gRPC當然是要先下載Library,從Official gRPC Releases這裡可以找到各版本的gRPC,點下C#後則可以看到有unitypackage的版本可以提供下載。拿下來後滙入,馬上就會看到錯誤。

Error: Could not load signature of Google.Protobuf.ByteString:get_Span due to: Could not load file or assembly

在網路上查詢後可以看到這篇討論,這個問題並不是gRPC端的問題,反而是Unity(或是更底層Mono)的問題。解決方法就是把dll檔全數(本來在runtime的不用挪出)放在同一個目錄下,就可以避免這個問題。一開始就碰到這個錯誤本來想說gRPC在Unity裡的支援性相當不好,不過解決這個問題後接下來到是相當的順利。

網路上的這篇文章有著簡單的連接範例可以做為參考。但在這一步的時候有些遲疑了。手邊的環境已在二個月前從Mac改成Windows 10,少了home brew的協助,總覺得在Windows上裝什麼都很麻煩,且很容易弄髒開發環境。當下想到的替代方案就是Docker。雖然gRPC沒有官方放置在Docker Hub的設定。但docker-protoc整理的不錯,就用它來解決proto compile。

原先下的command

protoc -I . --csharp_out=. --grpc_out=. simple.proto --plugin=protoc-gen-grpc=Grpc.Tools/tools/macosx_x64/grpc_csharp_plugin

換成Docker後要做一些修正

docker run -v `pwd`:/defs namely/protoc-all -f simple.proto -l csharp

將產生的二份檔案放入到Unity裡。並如文章裡所示,開立了二個cs檔,一個是server而另一個則是client。看似差不多完成時,卻想到server和client的build要分開才行,故額外定義了一個PreProcessor CLIENT_BUILD,在Cleint這放

#if CLIENT_BUILD
#endif

在Server端則是

#if !CLIENT_BUILD
#endif

試跑的結果相當的棒,用Unity Editor當server而windows build當client,完全符合執行結果。

利用這份當基底,接下來要改成配對連線。首先要修正的是server端,要在現有的程式碼中加入server端花很長時間尋找配對的模擬。故將其改成為

public override async Task<SimpleResponse> SimpleSend(SimpleRequest request, ServerCallContext context)
{
    // Some code before
    await Task.Delay(System.TimeSpan.FromMilliseconds(20000));

    var response = await Task.FromResult(new SimpleResponse {Text = text, Image = b64});
    return response;
}

而Client端一開啓時就會被卡住,因為原先的寫法會卡在Main Thread裡。故利用UniRx(用Task也行)進行修正,可以參考這篇文章更加了解UniRx的用法。簡單說就是讓UniRx自行開thread去執行,待完成後再回到Main Tread進行UI更新。

#if CLIENT_BUILD
Channel channel = new Channel("localhost:50051", ChannelCredentials.Insecure);
SimpleService.SimpleServiceClient client = new SimpleService.SimpleServiceClient(channel);

messageText.text = "Finding";

// Using UniRx for dealing with another thread rather than blocking main thread
Observable
    .Start(() =>
    {
        var response = client.SimpleSend(new SimpleRequest());

        Debug.Log("text: " + response.Text);
        byte[] im_bytes = Convert.FromBase64String (response.Image);
        File.WriteAllBytes(Application.dataPath + "/../client.jpg", im_bytes);
    })
    .ObserveOn(Scheduler.ThreadPool)
    .SubscribeOnMainThread()
    .Subscribe(x =>
    {
        messageText.text = "Done";
    });
#endif

並將這段Code從Start挪到某個button處理的callback裡。

Mirror Example Folder

開啓二個Client後測試,可以看到配對模擬的狀態有改變。

試驗gRPC的部份今天可謂相當的成功,不過接來下來,要將Server端抽離出來,利用別的語言來進行。


上一篇
大廳製作的概念
下一篇
Publisher Subscriber Usage
系列文
用Unity製作連線遊戲30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言