見出し画像

Now in REALITY Tech #34 転生したらAndroidエンジニアだった件 - 元Unityエンジニアの思うゲームとアプリの技術類似点

まえがき

はじめまして、REALITYでAndroidエンジニアをしているmits_sidです。

自分は最近REALITYへグループ会社間異動をしたエンジニアで、ここへ来る前はゲーム事業をやっている別グループ会社でUnityなどでゲームを作るゲームプログラマーをやっていました。

自分のAndroidアプリ開発の経験はAndroid 6.0の時代に軽く触った程度で、未知であるKotlinやモダンなAndroid開発環境の場に飛び込む今回の異動は自分にとっても挑戦でしたが、幸いなことになんとか(?)やることが出来ています。

なんとかやることが出来ているのはREALITYの他のAndroidエンジニアの方々に支えられていることももちろんですが、自分がこれまでに重ねてきた「Unityでのアウトゲームの開発」と「Androidアプリの開発」に類似点が見られることが大きいかなと思っています。

そこで今回は、ゲームプログラマーが如何にしてネイティブアプリのAndroidエンジニアへと「転生」できたのか、自分の思うそれぞれの技術的な類似点について書いてみたいと思います。

UnityのアウトゲームとモダンAndroidの技術的な類似点について

自分はこれまでUnityでゲームを作ってきたのですが、その中でも特に関わることが多かったのが「アウトゲーム」の開発です。

アウトゲームの定義は正確には定まってはおりませんが、「ゲームの本質部分に繋がるための準備機能や画面」を指すことが多いです。

一般的なモバイルゲームの場合は「バトルに入る前のパーティー編成画面」だったり「クエスト選択画面」「アイテム一覧画面」などが相当するかもしれません。

GUIプログラミング的な文脈

これらのアウトゲームの開発はアクションゲームのアクションパートにおけるロジックや物理演算、当たり判定といったものとは毛色が異なり、静的な画面UIを構築し、ユーザー操作によってロジックが走り、サーバーやローカルストレージとのやり取りが非同期で発生し、変化した状態がUIに反映されるという、一種のGUIプログラミングのような性質を持っていることが多いです。

iOSやAndroidのアプリ開発においてもUI構築、ロジック実装、状態変化のハンドリングとUIへの反映という、まざにGUIプログラミング的なことを行っております。

つまり、「アウトゲームの開発」と「アプリの開発」は本質的に同じ、あるいは近しいものであると自分は考えました。

これらの類似点をさらに掘り下げると、特に以下の部分が似ているかなと感じております。

  • 各画面とライフサイクル、遷移のモデリング

  • 推奨されるアーキテクチャのパターン

  • イベント駆動プログラミングを求められる

  • 非同期処理が必要になる

順番にこれらを見ていきたいと思います。

各画面とライフサイクル、遷移のモデリング

アウトゲームには必ず画面が存在します。例えば先程の例に挙げた「パーティー編成画面」や「クエスト選択画面」などです。同じく、アプリでも「チャット一覧画面」「チャット中の画面」などの画面が存在します。

それぞれの画面はアクセスしたときに生成され、離れたときに破棄されるという、一種のライフサイクルが存在していると考えることが出来ます。(パフォーマンスの観点から事前生成し常駐しているケースなどもありますが、一旦ここでは考慮しません)

そしてゲームに限らず、Androidアプリにおいてもチャット一覧画面→チャット中の画面といった形で、ある画面から画面へと遷移が発生します。

ゲームでもアプリでも、各画面のライフサイクルを考慮しつつ、画面遷移を如何にモデリングしていくか、ということが求められています。

Androidアプリ開発では公式でNavigationという仕組みが存在し、これらの画面遷移がサポートされています。このNavigationに乗る各画面はFragmentという形でモデリングされており、実装の上で各種ライフサイクルを管理する必要があります。(これが複雑で結構タイヘン)

Unityにおいては各画面と画面遷移におけるデファクトスタンダードは存在しない認識ではありますが(もしあったら是非教えて下さい)、Arbor3などのFinite State Machineを提供する外部アセットなどで実装されることがあります。この他にもUIマネジメントシステムのDoozy UI Managerや、OSSのUnity Screen Navigatorなど、実現方法は多岐に渡っています。

実現方法の違いはあれど、ライフサイクルに紐付いた画面とその遷移を管理する必要がある、という点においては同じと言えるでしょう。

推奨されるアーキテクチャのパターン

ソフトウェアのアーキテクチャは一般的には拡張しやすく、堅牢で、各種テストしやすくするアーキテクチャであることが重要とされています。

Androidアプリ開発ではGoogle公式でアプリアーキテクチャガイドがまとめられており、アプリの推奨アーキテクチャが提供されています。

これを眺めていると「UI Layer」「Domain Layer (optional)」「Data Layer」というレイヤー分けがなされており、UI Layerの内部で状態ホルダーとしてView Modelクラスを利用するという記述があります。これは関心の分離の原則に基づくレイヤードアーキテクチャを実現するMVVMアーキテクチャであると捉えることが出来そうです。

プログラミングにおいて一般的に踏襲すると望ましいと言われている原則はいくつかありますが、上記アーキテクチャガイドでは「関心の分離」に重きを置いています。自分の思うアウトゲームやアプリのプログラミングにおける関心の分離の重要ポイントは、「Viewとドメインロジックの隔離」という点です。

Androidアプリ開発でActivityやFragmentにすべての処理を記述してしまうのはありがちではありますが、これらのViewクラスは複雑なライフサイクルに支配されています。複雑なライフサイクルの上にドメインロジックが乗ると、Viewの都合とドメインロジック側の都合が入り混じり、クラスの担う責任範囲を曖昧にさせ、テストが困難になってしまいます。

同じようなことがUnityにおけるアウトゲーム開発でも当てはまります。

Unityにおいてゲーム画面を構成する各種UIはuGUIコンポーネントで構成されることが一般的ではありますが、これらのuGUIコンポーネントはMonoBehaviourであり、GameObjectの上にアタッチされて管理されます。そのため、これらのUIはUnity上のHierarchyとオブジェクトライフサイクルに支配されます。そのため、uGUIコンポーネントを利用したViewクラスと、それ以外のドメインロジックをいかに隔離するか、ということが各コンポーネントの責任範囲を明確にする上で重要となっていきます。

Unityは公式でアーキテクチャガイドは提供されておりませんが、OSSであるUnity向けDIツールのExtenjectVContainerなどを利用し、各種View層とModel層を分離し、Presenter層で協調させるMVPアーキテクチャとなることが多いかも知れません。

MVVMとMVPは厳密には異なるアーキテクチャ(Data BindingによるViewの更新があるかどうかなど)ではありますが、「Viewとドメインロジックの隔離」を目的とする点では同じといえるでしょう。

イベント駆動プログラミングを求められる

アウトゲームやアプリのプログラミングにおいてイベント駆動型プログラミングはとても相性が良く、各所でよく見られるパターンの一つです。

このパターンはGUIプログラミングでもよく用いられており、ある入力に対するロジックの発火とレスポンスとしてのUIへの反映などの各種出力を実現するのに適しています。

Androidアプリ開発では公式でKotlin FlowLiveDataなど、Androidの各種ライフサイクルに紐付けて利用しやすくなったイベントリスナー、イベントディスパッチャーの仕組みが存在します。特にKotlin FlowについてはCoroutineの一種であり、非同期なイベントメッセージ処理が扱いやすくなっています。

これらの仕組みがあると各種処理の実装が容易なだけではなく、先述したViewとドメインロジックの隔離がやりやすくなります。特にViewModelとFragmentの隔離がやりやすくなり、ViewModelの独立性が高まってテストが容易になります。

Unity (というよりC#) では、デリゲートモデルに基づいたイベント駆動の仕組みがevent構文でサポートされています。また、OSSのMessagePipeを利用するアプローチもあるでしょう。

これらを利用することでアウトゲーム部での各種ユーザ操作に連動して、複数のロジックが連動して動作するといった処理をより実装しやすく、よりテストしやすい構造を作ることが期待できます。

方式の違いはあれど、Androidアプリ開発でもUnityでもイベント駆動の仕組みがあることは同じです。

非同期処理が必要になる

アウトゲームやアプリに限らないことかもしれませんが、これらのプログラミングではWebAPIの通信やローカルストレージへのアクセス等が頻繁に発生します。

これらの処理を同期的に行うと画面が硬直し、AndroidではANR、ゲームでは体験の劣化に繋がります。そのため、これらの処理は非同期に処理する必要があり、加えて先述したライフサイクルの問題を考慮しながら行う必要があります。

具体的な例を挙げると、例えばゲームにおいてパーティ一覧画面に遷移したとき、そのパーティ一覧情報を非同期に取得している最中に他画面への遷移が走った場合、この非同期処理は適切にキャンセルされる必要があります。この辺りの処理が甘いと、既に存在しないパーティ一覧画面へのアクセスなどが発生し、実行時エラーなどでクラッシュを引き起こす可能性があります。

Androidアプリ開発ではKotlinがCoroutinesという仕組みを持っており、この辺りの非同期処理をライフサイクルと紐付けて処理しやすい環境が提供されています。各種非同期処理を実行するためのCoroutineを発火するとき、必ず何かしらのLifecycleScopeに紐付ける必要があり、紐付け先のLifecycleScopeが破棄されると同時に中断されるという性質を持っているため、画面に紐付いた非同期処理というものがやりやすい一面があります。

Unityの場合はモダン環境ではC#公式のTask、あるいはOSSのUniTaskを利用したasyncおよびawaitを使用した非同期プログラミングが一般的です。async/awaitは非同期処理をほぼ同期処理と同じように記述できるという、生産性を高める上ではとても強力な表現方法なのですが、一方でキャンセル処理を明確にロジック中に記載し、ハンドリングを行う必要がある、という一面も持っています。「非同期処理は何をもって中断されうるのか」ということを、開発者全員が意識する必要があるため、利用する際にはチームのリテラシーが求められるとも言えるでしょう。

また、async/await構文やUniTaskの登場する前の非同期処理ではUnityのCoroutinesを利用することが一般的でした。こちらはGameObjectに紐付ける必要があり、また実行するメソッドが必ずIEnumeratorの返り値である必要があったため、async/awaitほどは表現力が高くない一面がありましたが、同時にGameObjectに紐付けられるため、GameObjectが破棄される場合のキャンセルについては考慮しなくてもよい、という一面もあります。

おわりに

以上、Unityでのアウトゲーム開発とAndroidアプリ開発の技術的な類似点についてお伝えしました。

細かい部分はもちろん異なりますが、求められる要求に答えるためのアプローチにいくつかの類似点が見られることが分かったかと思います。

自分が今なんとか(??)やることが出来ているのは、ゲーム開発というフィールドで積み重ねることの出来たスキルが、これまでに挙げてきた記事のように技術的に類似点があり、結果としてアプリ開発のフィールドでも転化出来たということが大きいように思います。

Android開発は楽しく、アウトゲームに関わっていた開発者の方なら間違いなくコミット出来ると思います。

もし、今までアウトゲームをやっていたけどREALITY Androidを開発してみたい!と思った方は下記リンクよりご応募お待ちしております。もちろん、アウトゲームをやっていない方も興味があれば是非ともお待ちしております。