ID:53945さん

3年後の目標や野望


ゲーム開発のアーキテクトとして、高い志をもつメンバーと最強なランタイム基盤の開発と開発環境を構築したい

ゲーム開発を10年経験して、ゲーム開発の不確実性の高さを強く実感した。 近年は特に市場が求めるクオリティレベルが上がっており、開発が大規模化・長期化している。 タイトルによる違いはあるものの、ゲームで共通して求められるランタイム基盤や開発環境に対する要求は普遍的な部分が非常に多く、またそこの品質が開発のイテレーションの速度に直結すると感じている。 不確実性の削減のために、市場のニーズに応えるために、開発イテレーションの速度を向上させるために、車輪の再開発を防ぐために、最強なランタイム基盤の開発と開発環境を構築したいと考える。

プロジェクト経験

2023年/3ヶ月以内

アセットダウンロードの性能問題を解決するためのアセットダウンロード基盤の開発

# 問題意識 ある程度の規模のゲームアプリを運用する上では、アセットダウンロード機能は必須な機能要件である。機能を一言で表すと、「アプリが使用するアセットをアプリ更新なしで取得・更新する機能」だが、これにまつわる非機能な要求がいくつかある。また、往々にしてこの非機能な要求が切実でかつ対応が難しい。 ここでの「難しい」という意味は、ある非機能な要求に応えるためには - 他の非機能な要求とトレードオフな部分もある - 世の中的にも難易度の高いソリューションしか見当たらない - Unityにおけるアセット配布のスタンダードでは非機能な要求を満たしにくい(融通が効きにくい、改善の余地が少ない) - 部分的な実装の差し替えが出来ず、大規模・広範囲の自前実装が求められる - 特に通信部分を自前実装する場合、ネイティブ(iOS, Android)の実装が存在しないため、この部分の技術的な難易度が高い - 上記のようなランタイム周辺の知識だけでなく、非ランタイム(ビルド&デプロイ環境)も含めての知識など、求められる技術的な知識の範囲が特段広い - そういった事前知識だけでなく実際の検証から得られる知識も必要であり、少なくとも「クライアントエンジニアは対応できて当たり前」とは絶対に言えない - 特に端末の性能から最大のパフォーマンスを出すためのチューニングは事前知識と多くの実験が必要になる といった、絶対的な正解がなく、要求を叶えるためには思った以上に(※1)大きなコストをかけなければならないという意味である。 <aside> (※1)少なくともアセットダウンロードのシステムを作る・知る前と後でそこに大きなギャップがあるだろうと感じる。詳しく知らない状態だと、上のように「難しさ」を掻い摘んで説明されてしまうため、「よくわからないけど、思った通り本当によくわからなかったから諦める」ということになりがちである。そもそも問題を捉えること・捉えてもらうことにすら労力が思った以上にかかるということである。 それ以前に、そもそも開発期に明確に要求されることすらなく、言わなくても当たり前に必要なレベルな機能であると認識されているし、認識している。その割に非機能な要求は多く、それらが複雑に絡み合い難しい、かつ運用が始まらないと非機能な問題が顕在化しない、運用が始まると制約が増え余計に改善が難しくなるという厄介さがある。 ---- # 非機能な要求 ここではどのような要求があるかを列挙するだけに留める。 ## ランタイム - キャッシュウォームアップにかかる時間 - ハッシュ比較にかかる時間 - ハッシュ比較時にかかる負荷(FPSの低下、端末の温度上昇) - ダウンロードにかかる時間 - ダウンロード時にかかる負荷(FPSの低下、端末の温度上昇) - ダウンロードするファイルのサイズ(通信量) - ファイルを端末に保存するのにかかる時間 - ファイルを端末に保存する時にかかる負荷(FPSの低下、端末の温度上昇) - ハッシュを端末に保存するのにかかる時間 - 端末に保存するファイルのサイズ(使用ストレージ) - キャッシュの寿命延長処理にかかる時間 - キャッシュの寿命延長処理時にかかる負荷(FPSの低下、端末の温度上昇) - アセットのロードにかかる時間 - アセットのロード時にかかる負荷(FPSの低下、端末の温度上昇) 以下は開発アプリのみに要求される機能 - ブランチ別にアセットをダウンロードできる - 端末内に保存されたアセット > ダウンロードすべきアセット一覧の場合に、端末内の不要なアセットを削除できる ## 非ランタイム これに関しては別プロジェクトとして記載するので省略。 ## ランタイムの課題 - アセットバンドルのダウンロードをUnityにお作法に則って実装するとパフォーマンスが悪い - パフォーマンスをチューニングしようにもI/Fの役割が大きすぎたり、UnityAPIがメインスレッド以外で実行できないなど制約が大きすぎる --- # 課題の解決手段 - UnityAPIに頼らず自前でアセットバンドルをダウンロードしたい! - UnityWebRequestAssetBundleやCachingを使わない - 通信、保存、ハッシュ比較・保存を自前でやる - アセットバンドルのダウンロード通信はHttpClient(HTTP/2.0対応させる)、保存はSystem.IO - ハッシュはCSVにSystem.IOで書き込み、比較時はそれをロードする - これらが非UnityAPI置き換えられるためワーカースレッドで並列で動かせるようになり速度が劇的に改善される   - Cachingはアセットバンドルの数が増えるとパフォーマンスの劣化が激しいが、その問題からも解放される - LZMA->LZ4への再圧縮と(必要に応じての)CRCの比較はAssetBundle.RecompressAssetBundleAsyncを使う - UnityWebRequestAssetBundleはアセットバンドルのロードも兼ねているが、Caching外のパスに保存することになるのでロード時はAssetBundle.LoadFromFileAsyncを使うようにする ## 副次的な恩恵 - Caching.readyの待機もしなくてよくなる! - これが完了しないとアセットバンドルをロードできない - アセットバンドルの数に比例して時間がかかる - 運用が続くとかなり待たされるようになっちゃう - スペックによるけど数分もあり得る - 端末内のアセットバンドルが自動で削除されなくなる! - Caching管理のアセットバンドルはN日で寿命を迎え勝手に削除されちゃう - 最大で150日に指定することができる - 最後にそのアセットバンドルをロードした時刻からN日後に削除される - 既存案件では定期的に全アセットバンドルのタイムスタンプを更新することでそれを回避している - この更新処理も結構重いのでそもそも勝手に消えないならそっちの方がいい! --- # 成果 - この対応は新規開発プロジェクト①・②と運用中のタイトル「ツイステッドワンダーランド」に適用した - 新規開発プロジェクトではまだアセットの数が少なく効果測定をしても効果を感じにくいため、「ツイステッドワンダーランド」で効果測定をした - どの環境でも速度は改善し、特にAndroidでは4倍以上の速度が出るパターンもあった - アセットバンドル化しないアセット(具体的にはCRIWareの音声や動画)のダウンロードに関しても別途実装したが、HTTP/2.0化、ワーカースレッドで並列処理に関しては同じ基盤機能を利用した - アセットバンドル化しないアセットをRawDataと呼び、これのダウンロード時間も計測してある ## ダウンロードファイル数・サイズ・所要時間 ### テストパターン | | アプリバージョン | ワーカースレッド数(AssetBundle・RawData) | OS | OSバージョン | 端末モデル | ネットワーク環境 | | --- | --- | --- | --- | --- | --- | --- | | パターン101 | 対応前 | | iOS | 15.0 | iPhone 13 Pro Max | オフィス | | パターン102 | 対応後 | 20・20 | iOS | 15.0 | iPhone 13 Pro Max | オフィス | | パターン103 | 対応後 | 50・20 | iOS | 15.0 | iPhone 13 Pro Max | オフィス | | パターン111 | 対応前 | | iOS | 15.0 | iPhone 13 Pro Max | 自宅 | | パターン112 | 対応後 | 20・20 | iOS | 15.0 | iPhone 13 Pro Max | 自宅 | | パターン113 | 対応後 | 50・20 | iOS | 15.0 | iPhone 13 Pro Max | 自宅 | | パターン201 | 対応前 | | Android | 12 | Xiaomi 12T Pro | オフィス | | パターン202 | 対応後 | 20・20 | Android | 12 | Xiaomi 12T Pro | オフィス | | パターン203 | 対応後 | 50・20 | Android | 12 | Xiaomi 12T Pro | オフィス | | パターン211 | 対応前 | | Android | 12 | Xiaomi 12T Pro | 自宅 | | パターン212 | 対応後 | 20・20 | Android | 12 | Xiaomi 12T Pro | 自宅 | | パターン213 | 対応後 | 50・20 | Android | 12 | Xiaomi 12T Pro | 自宅 | ### iOS #### パターン101 対応前アプリ、iOS,、iPhone 13 Pro Max、オフィス | | AssetBundle | RawData | | --- | --- | --- | | ファイル数 | 28042 | 4922 | | サイズ | 3.7GB | 2.7GB | | 所要時間 | 4分58秒 | 1分35秒 | #### パターン102 対応後アプリ、iOS,、iPhone 13 Pro Max、オフィス、ワーカースレッド数(20・20) | | AssetBundle | RawData | | --- | --- | --- | | ファイル数 | 28051 | 4922 | | サイズ | 3.7GB | 2.7GB | | 所要時間 | 4分11秒 | 1分54秒 | #### パターン103 対応後アプリ、iOS,、iPhone 13 Pro Max、オフィス、ワーカースレッド数(50・20) | | AssetBundle | RawData | | --- | --- | --- | | ファイル数 | 28051 | 4922 | | サイズ | 3.7GB | 2.7GB | | 所要時間 | 2分24秒 | 1分26秒 | #### パターン111 対応前アプリ、iOS,、iPhone 13 Pro Max、自宅 | | AssetBundle | RawData | | --- | --- | --- | | ファイル数 | 27834 | 4907 | | サイズ | 3.7BG | 2.7GB | | 所要時間 | 8分12秒 | 4分23秒 | #### パターン112 対応後アプリ、iOS,、iPhone 13 Pro Max、自宅、ワーカースレッド数(20・20) | | AssetBundle | RawData | | --- | --- | --- | | ファイル数 | 27843 | 4907 | | サイズ | 3.7GB | 2.7GB | | 所要時間 | 6分21秒 | 4分11秒 | #### パターン113 対応後アプリ、iOS,、iPhone 13 Pro Max、自宅、ワーカースレッド数(50・20) | | AssetBundle | RawData | | --- | --- | --- | | ファイル数 | 27843 | 4907 | | サイズ | 3.7GB | 2.7GB | | 所要時間 | 5分41秒 | 4分9秒 | ### Android #### パターン201 対応前アプリ、Android、Xiaomi 12T Pro、オフィス | | AssetBundle | RawData | | --- | --- | --- | | ファイル数 | 28042 | 4922 | | サイズ | 3.0GB | 2.7GB | | 所要時間 | 19分11秒 | 5分51秒 | #### パターン202 対応後アプリ、Android、Xiaomi 12T Pro、オフィス、ワーカースレッド数(20・20) | | AssetBundle | RawData | | --- | --- | --- | | ファイル数 | 28051 | 4922 | | サイズ | 3.0GB | 2.7GB | | 所要時間 | 5分15秒 | 2分52秒 | #### パターン213 対応後アプリ、Android、Xiaomi 12T Pro、オフィス、ワーカースレッド数(50・20) | | AssetBundle | RawData | | --- | --- | --- | | ファイル数 | 28051 | 4922 | | サイズ | 3.0GB | 2.7GB | | 所要時間 | 5分17秒 | 2分4秒 | #### パターン211 対応前アプリ、Android、Xiaomi 12T Pro、自宅 | | AssetBundle | RawData | | --- | --- | --- | | ファイル数 | 27832 | 4907 | | サイズ | 3.0GB | 2.7GB | | 所要時間 | 21分47秒 | 4分39秒 | #### パターン212 対応後アプリ、Android、Xiaomi 12T Pro、自宅、ワーカースレッド数(20・20) | | AssetBundle | RawData | | --- | --- | --- | | ファイル数 | 27843 | 4907 | | サイズ | 3.0GB | 2.7GB | | 所要時間 | 6分30秒 | 6分35秒 | #### パターン213 対応後アプリ、Android、Xiaomi 12T Pro、自宅、ワーカースレッド数(50・20) | | AssetBundle | RawData | | --- | --- | --- | | ファイル数 | 27843 | 4907 | | サイズ | 3.0GB | 2.7GB | | 所要時間 | 4分52秒 | 4分9秒 | --- # 実現したカスタマイズ - アセットバンドルのダウンロードに使う通信クライアントをHttpClientに載せ替える - 非同期かつキャンセル可能であること - アセットバンドルの保存をCaching使わずに行う - .NetのIOで保存する - LZMAからLZ4に再圧縮する - [https://docs.unity3d.com/ja/2019.4/Manual/AssetBundles-Cache.html](https://docs.unity3d.com/ja/2019.4/Manual/AssetBundles-Cache.html) - [https://docs.unity3d.com/ja/2019.4/ScriptReference/AssetBundle.RecompressAssetBundleAsync.html](https://docs.unity3d.com/ja/2019.4/ScriptReference/AssetBundle.RecompressAssetBundleAsync.html) - ハッシュ比較・書き込み・保存をCachingを使わずに行う - .NetのIOで保存する - ダウンロードすべきアセットの数・サイズをCaching使わずに取得する --- # 実装した内容 デフォルトの実装の概要をまず知ってほしい。 その上で変えた内容のポイントを説明する。 <aside> **デフォルト(AssetBundleResource)の実装の概要** - ダウンロードすべきかの判定 - AssetBundleRequestOptions(Location.Dataそのもの)にComputeSizeというvirtualメソッドがありそれがダウンロードサイズを返している - Caching.IsVersionCacheを使ってそのハッシュのバンドルが端末に保存済みか確認し、保存済みの場合は0を返している - 保存済みではない場合(持ってないもしくはハッシュが変わっている場合)、ビルド時に計算したサイズを返している - 返した値が0より大きいかで判定している - リモートバンドルの場合は端末内にあろうがなかろうがUnityWebRequestAssetBundleを使ってロード・ダウンロードしてる - Locationが持つ情報(バンドルのURL、CRC、ハッシュなど完全な情報を持ってる)を使ってUnityWebRequestAssetBundleを発行してWebRequestQueueにそれを積む - WebRequestQueueは順次溜まっているリクエストを送信していく - このリクエストでUnityWebRequestAssetBundleが以下を全部やってくれる - アセットバンドルのバイナリダウンロードおよびCachingのパスに保存 - 保存時にLZMAからLZ4へ再圧縮 - サーバー上のアセットバンドルのCRCとリクエストに付与したCRCを比較し、違った場合はリクエストをエラー扱いに - リクエストの完了をWebRequestQueueOperationを介して受け取り、成功か・エラーかでハンドリング - 成功の場合 - リクエストからアセットバンドルを取り出す - コールバックを蹴り上層のオペレーションに返す - エラーの場合、リトライ回数に応じてリトライ </aside> **CustomAssetBundleResourceにおける変更内容概要** - デフォルト実装のI/F、登場人物の役割をなるべく変えず、実装を置き換えたイメージ - 人物名は変えてあるのでそれに倣ってI/Fの名前が少し変わってる部分もある - ダウンロードすべきかの判定 - AssetBundleRequestOptionsを継承したCustomAssetBundleRequestOptionsを実装 - ComputeSizeをoverride - File.IOによりアセットバンドルが保存済みか、ハッシュが変わっているかで判定 - Locationが持つ情報(バンドルのURL、CRC、ハッシュなど完全な情報を持ってる)を使ってHttpClientに依頼するHttpRequestを発行してAssetBundleDownloadRequestQueueにそれを積む - AssetBundleDownloadRequestQueueは順次溜まっているリクエストをHttpClientに投げていく - 取得したアセットバンドルのバイナリ、通信例外をHttpRequestQueueOperationを介してCustomAssetBundleResourceに返す - CustomAssetBundleResourceはそれを受け取り、以下を行う - バイナリをアセットバンドルとして端末(Application.persistentDataPath)に保存 - 保存後、LZMAからLZ4へ再圧縮 - この時にCRC比較 - 結果が返ってくるので、成功以外のステータスの場合例外を投げる - CRCが異なる場合、ステータスはNotMatchingCrc - 詳細はUnityEngine.AssetBundleLoadResultを見て - ハッシュをCSVに保存 ## **上記で登場する非同期処理のキャンセルの実現方法** - あるべき論としては非同期メソッドはCancellationTokenを受け付けるべきである - AddressablesはAPIはキャンセルできるようになってない - CancellationTokenを渡していくようにし、OperationCanceledExceptionを投げるように内部に手を入れていくのは大変すぎる - UniTaskでラップすることガワとしてはキャンセルできる(OperationCanceledExceptionが投げられ次のステップに進める) ```csharp handle = UnityEngine.AddressableAssets.Addressables.DownloadDependenciesAsync(key); await _taskAgent.Run(this, ct => handle.ToUniTask(Progress.Create(onProgress), cancellationToken: ct), cancellationToken); ``` - しかし、このhandle自体の進行は止まっておらず、あくまで購読をやめただけである - そこでAssetBundleDownloadRequestQueueにTerminateのI/Fを実装して通信、保存を中断するようにした ```csharp try { await _taskAgent.Run(this, ct => handle.ToUniTask(Progress.Create(onProgress), cancellationToken: ct), cancellationToken); } catch (Exception ex) { GlobalContainer.Resolve<AssetBundleDownloadRequestQueue>().Terminate(); if (ex is OperationCanceledException) { throw; } throw new AssetBundleDownloadException(ex.Message, ex); } ``` - AssetBundleDownloadRequestQueueにCancellationTokenSourceを持たせておき、Terminateでキャンセルを発火する - そのトークンはアセットバンドルのダウンロード通信や保存などの非同期処理に渡してあるのでTerminateをきっかけにしてそれが中断できる - 中断時はエラーコールバックを蹴り上層のオペレーションに返す(オペレーション自体もエラーとして終わらせる) --- ## デフォルトをコピーや継承してカスタムしたクラス一覧 **public class CustomAssetBundleProvider : ResourceProviderBase** AssetBundleResource→CustomAssetBundleResourceを扱うように変更。 **public class CustomAssetBundleResource : IAssetBundleResource** 上記概要参照。 **public class CustomBundledAssetProvider : ResourceProviderBase** 現状ただのコピー。 実装の差し替えの余地を残すために一応差し替えてある。 **public class CustomAssetBundleRequestOptions : AssetBundleRequestOptions** ComputeSizeをoverrideしてる。 **public class CustomBuildScriptPackedMode : BuildScriptBase** アセットバンドルのビルドスクリプト。 カタログに記載するAssetBundleRequestOptionsを作っている部分をCustomAssetBundleRequestOptionsを作るように変更。 それだけのために2000行近いクラスをコピーしてる。 継承も考えたがvirtualメソッドの実装がデカすぎて結局デフォルト側に手を入れる箇所が増えてしまうのでコピーにした。

2023年/2年以上

ネイティブ共通基盤の開発

# 新規プロジェクト①・②および今後の新規開発でも利用する共通基盤の開発 ## 課題 - プロジェクトの開発フェーズの進行に合わせて人員を増加させていくため、なるべく早い段階で高速に開発・不具合修正を進められる開発環境を構築する必要があった。 - アプリのレイヤ設計において、責務の明確化と依存関係の整理を通じて、仕様と技術の分離を実現し、保守性・テスタビリティの高い構造を構築する必要があった。 - 今後の人員増加・配置の変更にも備えて、属人性を削減していく必要があった。 - アーキテクチャや設計にルールを敷いていく必要があった。 - コンテンツに依存しない共通機能を共通基盤として開発する必要があった。 - 不具合が発生した際に、原因の特定と修正を迅速に行えるよう、アプリ全体でのエラーハンドリングの仕組みを整備する必要があった。 - 開発ツールやCI/CD環境について、構築・実行の高速化や障害時の検知・復旧対応の向上といった非機能要件に、より高い水準で応える必要があった。 - 複数の開発メンバーが円滑に作業を進められるよう、コードの可読性を高め、要点を文書化し、定期的な情報共有の仕組みを構築する必要があった。 - 本番向けの機能要件に加え、テスト・デバッグ・量産支援など開発者・デザイナー向けの要件にも対応できるよう、柔軟な実装と支援ツールを提供する必要があった。 - 演出再生やTimelineなどのコンテンツ実装において、汎用的なワークフローや再生環境を整備することで、今後の拡張性を確保しておく必要があった。 - 使用している外部パッケージやミドルウェアを最新化し、将来的な保守・運用負荷を軽減するために、段階的なアップデートを計画的に進める必要があった。 - アセット作成に関する他チームのワークフローについても、連携しやすい形で設計・整備を進める必要があった。 --- ## 自身の役割・チーム体制 **チーム体制** 新規プロジェクト①: 自分含め3-6名(フェーズ進行に合わせて徐々に増員) 新規プロジェクト②: 自分含め2-10名(フェーズ進行に合わせて徐々に増員) このうち、共通基盤の開発にコミットするメンバーは自分含めて2-4名であり、これを共通基盤チームと呼称している。 **自身の役割** - 共通基盤チームのリーダー - 新規プロジェクト①・②のネイティブアーキテクト - 共通基盤開発の目的・マインドの周知 - 工夫したこと・成果として次項に記載 - 半期毎に開発対象や行動指針をチーム目標として設定 - 毎月進捗を振り返り、目標達成できるようにスケジュール調整 - 共通基盤全体の7~8割は自身が開発担当 - 後述する開発内容は全て自身が設計・実装を担当した箇所 - 特にアーキテクチャ、ルール、共通機能、エラーハンドリング、環境構築全般など、影響範囲が大きい箇所を積極的に対応 - プロジェクト毎のコンテンツ開発には主に設計・レビュワーを担当 - アーキテクチャに則るように、共通機能が使われるようにメンバーをサポート - コンテンツを複数跨いで利用すべき共通機能の設計・実装を担当 --- ## 工夫したこと・成果 - アプリ全体の設計にクリーンアーキテクチャを採用し、責務の明確化・依存関係の整理を通じて、テスタビリティや保守性の高い構造を構築した。 - チームの属人性を下げるため、アーキテクチャや設計方針に関するルールとコーディング規約・ガイドラインを明文化し、開発者全体が共通理解を持てるようにした。 - 可読性の向上と属人性の排除を推進するため、フォーマッター設定(editorconfig、Rider設定)を整備し、プロジェクト内のコードスタイルを統一。 - 複数プロジェクトで再利用可能な機能を共通機能として切り出して実装し、プロジェクト固有のロジックとの分離を徹底した。 - アプリ全体における例外処理の方針を整備し、共通の例外クラスを実装。例外発生時にはルート文脈でキャッチし、ユーザーへの適切なエラー表示や復旧処理を可能にした。 - CI/CD環境については、Jenkins・Docker・CLIツールを連携させた構成により、ビルド・テストの自動化を実現。 - CI/CD環境の構成とそれに関するスクリプトをすべて永続化して管理することで、障害や環境変更に強い状態にした。 - これに関しては成果が大きく特にアピールしたいポイントなので「アセットビルド環境に対する非機能な要求に応える」として別プロジェクトに詳細を記載 - デバッグやテスト作業の効率化のため、ビューワー、モック表示などを実装。切り出し再生や再現性確認を支援した。 - デザイナー支援として、UnityEditor上でのアセット単体再生や実機での確認用シーンを整備し、アセット開発のイテレーションを高速化した。 - Unity本体や使用パッケージ群、Jenkinsとそのプラグインのバージョンを順次アップデートし、将来的なメンテナンス性と安全性を担保した。 - アセット作成に関する他チーム向けのワークフローを整備することでヒューマンエラーを削減した。 - 様々なデバッグ用のパッケージを導入したり独自のデバッグ機能を実装することで、多角的に負荷検証や障害検知ができるようにした。また、それらデバッグ機能のエントリポイントを統合することで、誰でもデバッグ機能の存在を認知できるようにした。 --- # 成果を出すために対応した開発 ## ランタイム共通基盤の開発 - コードフォーマットの設定 - .editorconfigの設定 - Riderの設定 - アセットビルド、アプリビルド - Unity内の構成 - コマンドラインAPIの実装 - コードアーキテクチャ - クリーンアーキテクチャ - asmdefの利用 - DIコンテナの利用 - 依存関係逆転、実装の差し替え - アセットロード - 非同期ロード・キャンセル・解放 - 解放漏れ防止や不必要な多重ロードの防止策としてプロキシを実装 - アセットダウンロード - これに関しては成果が大きく特にアピールしたいポイントなので、「アセットダウンロードの性能問題を解決するためのアセットダウンロード基盤の開発」として別プロジェクトに詳細を記載 - ここでは簡単な概要説明までとします - AssetBundleのダウンロードの最適化も行った - アセットバンドルのダウンロードをUnityにお作法に則って実装するとパフォーマンスが悪い - パフォーマンスをチューニングしようにもI/Fの役割が大きすぎたり、UnityAPIがメインスレッド以外で実行できないなど制約が大きすぎる - なのでUnityAPIに頼らず自前でアセットバンドルをダウンロードするようにした - UnityWebRequestAssetBundleではなくHttpClientを使う - HTTP/2.0にも対応させる - Cachingを使わない - 通信、保存、LZMA->LZ4への再圧縮、CRCの比較、ハッシュ比較・保存を自前でやる - UnityAPIを使わなくした箇所はワーカースレッドで並列処理で動かすようにする - Addressablesのカスタマイズで上記を実現 - 非AssetBundleのアセット(CRIWareのサウンド・ムービーアセット)のダウンロードに関してもHttpClient、HTTP/2.0、ワーカースレッドでの通信・保存することで高速化 - エラーハンドリング - エラー概要がわかりやすいように例外クラスを実装 - アプリケーション実行やコンテンツ実行のルート文脈で例外をキャッチし、その型により適切なメッセージを表示したり、復旧フローに促すように - フロントエンド接続先変更機能 - テスト環境、オペレーション環境、本番環境など、ランタイムで接続先を変更できるように - ダウンロードアセット接続先変更機能 - アセットはアセットリポジトリのブランチ名をディレクトリ名としてコンテンツサーバーにデプロイするように - その接続先のディレクトリ名をランタイムで選べるように - WebView組み込み - UniWebView - Vuplex - フロントエンドとの通信プロトコル - WebNativeコマンド - Web→ネイティブに対しての命令のこと - サーバーAPIのようにリクエスト・レスポンスの形で命令の引数と結果の受け渡しが行われるように - レスポンスには成否と失敗の場合のエラーコードやエラーメッセージを含むようにし、フロントエンドでエラーハンドリングができるように - サウンド基盤 - CRIWareを利用 - CueSheetの非同期ロード・キャンセル・解放 - Playerの生成・破棄 - 非プレイモードのサポート - CancellationTaskAgentWithLifetime - クラスなどのインスタンスの寿命と紐付けて非同期処理をキャンセルする仕組み - 各種デバッグツールの組み込みと統合 - DebugSheet - SRDebugger - In-GameDebugConsole - Graphy - Proxima - WorkerThreadPool - ワーカースレッドに同期・非同期処理を投げて完了や例外を受け取れるクラス - UnityLogging組み込み - テストの構成 - TestRunnerを利用 - PlayModeで結合テスト、EditModeで単体テストを実装 - コマンドラインから呼び出せるように - CI/CDへの組み込み - JenkinsのジョブからコマンドラインのAPIを呼び出す - テスト開始とテストの成否のタイミングでGitHubのリポジトリコミットステータスを付与 - リポジトリ更新時Webhookで上記ジョブを実行 - ScreenResolutionUtils - スクリーン解像度、仮想スクリーン解像度の取得 - CoordinateUtils - スクリーン座標、仮想スクリーン座標、ワールド座標の相互変換 - サウンドとStreamingAssetsからのロードとDL先からのロードの切り替え - 非プレイモードでのサウンドロード・再生 - Timelineからのサウンド再生 - 非ランタイム(プレビュー) - 倍速、シーク - InputSystemを用いたスクリーンジェスチャーの購読 - PerspectiveCameraのFOV計算、OrthographicCameraのサイズ計算 - パッケージの自前管理&パッチ管理 - UniTaskの拡張 - UniTask.WhenAllは引数のUniTaskが1個でも例外スローすると他のUniTaskの完了や成否は検知できない - UniTask.WhenAllWithReportsを実装 - 引数のUniTaskが成否含めて全て完了するまで必ず待機する - 成否や失敗の場合の例外はReportsとして返り値として受け取れる ## 開発環境の構築・運用 - Unityプロジェクト - ローカルアセット、リモートアセット、アセットビルド設定とはリポジトリを分けている - ローカル環境構築時にそれらのリンクを貼ることでローカルで統合させている - ローカルアセット - リモートアセット - 各種自動化スクリプト - アセットビルド、アプリビルド&デプロイ、アセットバリデーション、テスト、Unityプロジェクト環境構築自動化などのスクリプト - Jenkins構成・管理 - Jenkinsジョブ - SharedLibraryを作って共通処理やコア実装をライブラリ化 - Webhookを使ってリポジトリ更新時に自動でテスト、アセットビルドのジョブが実行されるように - アプリビルド - Unityプロジェクト実装からJenkinsジョブ組み込みまで - アセットビルド - これに関しては成果が大きく特にアピールしたいポイントなので、「アセットビルド環境に対する非機能な要求に応える」として別プロジェクトに詳細を記載

2023年/3ヶ月以内

アセットビルド環境に対する非機能な要求に応える

# 課題 リモートアセットをビルドしてサーバーにデプロイする仕組み・環境はこれまでのどのプロジェクトでも実現してきた。 しかし、社内で統括的にこのビルド環境を設計・実装・運用するチームや担当者などは存在せず、プロジェクト毎に担当者を決めて実現してきた。 プロジェクト毎にJenkinsを立ててワンボタンでアセットビルドからデプロイまで完了させる最低限のワークフローは実現できていたが、非機能な要求に対して十分に応えることができない課題があった。 特にプロジェクトの運用フェーズや開発終盤のアセットが大量生産されるフェーズにおいて、アセットビルド環境がボトルネックとなりテストのイテレーション速度が低下したり、障害復旧に多大なコストがかかったりする問題が起きていた。 --- # 非機能な要求 非機能な要求とはざっくり列挙すると以下のようなものである。 - ビルド&デプロイが簡単に実行開始できる - ビルド開始からデプロイ終了までの時間が短い - 環境が壊れにくい - もし壊れてもすぐに復旧できる - 環境の変化に適応できる もう少し精緻化すると以下のようになる。 - JenkinsなどCI/CDから少ない手順でビルド&デプロイ開始のジョブを実行できる - ビルドワークスペースのセットアップにかかる時間が短い - ビルドにかかる時間が短い - デプロイにかかる時間が短い - 環境が壊れにくい - 依存システム、ツール類のアップデートが容易 - もし壊れてもすぐに復旧できる - 環境やスクリプトが永続化されている - ゼロから再構築が容易 - システムを構成する要素の部分的取り替えが容易 - 障害復旧が容易 --- # 工夫したこと ## 簡単にアセットのビルド&デプロイができるように Jenkinsを構築し、Manage and Assign Rolesプラグインを使ってユーザー毎の権限を管理した。 ユーザーに対して必要最低限の権限を付与することで、ワークフロー上で不必要な導線を非公開にでき、実行したいジョブを間違えずに実行できるようにしました。 ## 各種プロセスにかかる時間を短縮 - ビルドワークスペースのセットアップにかかる時間 - ビルドにかかる時間 - デプロイにかかる時間 この三つに要する時間がなるべく短くなるようにした。 ### ビルドワークスペースのセットアップにかかる時間の短縮 CI/CDツールによってはデフォルトではビルド毎にワークスペースが作られるものもあるが、Jenkinsを採用することでジョブ毎にワークスペースが維持され、ジョブに対してビルドするたびにワークスペースを再構築する必要がないため、時間短縮に繋がる。 アセットをビルドするためのワークスペース内の構成が最小になるように、構成要素を役割毎に別リポジトリで管理するようにした。これにより、リポジトリ1個ずつのサイズも小さくなるため、クローン・プルなどのGit操作にかかる時間も短縮される。 具体的には以下のような構成を取ることにしている。 ->で書いてあるディレクトリは別リポジトリ管理であり、リンクを張ることでこの構成を取る。 - ジョブ名 - Unityプロジェクト - Assets - AddressableAssetsData → shared-assets-build-settings/AddressableAssetsData(Addressablesのビルド設定全般) - AssetGroups - Local → local-assets/AddressableAssetsData/AssetGroups/Local(ローカルアセットのビルドグループ設定) - Remote → remote-assets/AddressableAssetsData/AssetGroups/Remote(リモートアセットのビルドグループ設定) - ContentStateCache → assets-build-cache/ContentStateCache(ビルドキャッシュ) - LocalAssets → local-assets/LocalAssets(ローカルアセット) - RemoteAssets → remote-assets/RemoteAssets(リモートアセット) - Library - BuildCache → assets-build-cache/BuildCache(ビルドキャッシュ) - com.unity.addressables - aa → local-assets-build-contents/aa(ローカルビルドコンテンツ) - ServerData → remote-assets-build-contents/ServerData(リモートビルドコンテンツ) - shared-assets-build-settings - AddressableAssetsDataなど、アセットビルドに関する設定ファイルを管理するリポジトリ - local-assets - アプリにビルトインするアセットを管理するリポジトリ - remote-assets - ダウンロード対象となるアセットを管理するリポジトリ - assets-build-cache - ビルド中間生成物を管理するディレクトリ、BZ2に圧縮してAWS S3で管理 - native-scripts - ShellScriptやPythonスクリプトなど、リンク作成・ビルド・デプロイに必要なスクリプト群を管理するリポジトリ - local-assets-build-contents - local-assetsをビルドして生成されたアセットバンドルを管理するリポジトリ - remote-assets-build-contents - remote-assetsビルドして生成されたアセットバンドルを管理するディレクトリ - AWS CloundFrontで配信するためにAWS S3にデプロイする - キャッシュとして管理するために圧縮して別のバケットにもデプロイ ### ビルドにかかる時間の短縮 ビルドワークスペースの構成の工夫により、ビルド中間生成物とビルドして生成されたアセットバンドルの両方を管理しており、最適な差分ビルドが保証されるため、ビルドにかかる時間が最短になる。 それらはリポジトリによるブランチ管理やS3のディレクトリ構成によりブランチ毎にしっかりとバージョン管理されているため、ワークスペースが作り直されたとしてもビルドにかかる時間は最短になる。 また、ローカルアセットのビルドジョブではリモートアセットに関するリポジトリやディレクトリをワークスペース内に構成する必要がないため、最小単位で最速でビルドができるようになっている。 ### デプロイにかかる時間の短縮 これに関してもビルドワークスペースの構成の工夫により、ビルドして生成されたアセットバンドルをバージョン管理しているため、最適な差分デプロイが保証されるため、デプロイにかかる時間が最短になる。 ## 環境が壊れにくくする&もし壊れてもすぐに復旧できる環境を作る 環境が壊れにくくするため、環境依存をなるべく少なくするために、JenkinsをDockerコンテナで動かすようにした。 jenkins/jenkinsイメージをベースにしてカスタマイズしたイメージを構成し、Docker Composeで起動するようにした。 これにより、Jenkinsのアップデートやプラグインの更新などが容易になり、環境の変化に適応しやすくなった。 AWS EC2上のLinuxインスタンス上で上記を動かすようにした。 また、Jenkinsの設定を永続化するためにConfiguration as Codeプラグインを使用し、Jenkinsの設定をコード化してリポジトリで管理するようにした。 ジョブの設定に関しても設定のXMLファイルを同じリポジトリで管理するようにした。 ジョブで使用するDeclarative Pipelineスクリプトは別リポジトリで管理するようにした、 また、そこで利用する共通処理はSharedLibrary化し、これはまた別のリポジトリで管理するようにした。 --- # 成果 狙い通りアセットビルド環境がボトルネックとなりテストのイテレーション速度が低下したり、障害復旧に多大なコストがかかったりする問題は解消された。 ## ビルドジョブの完了時間の比較 ワークスペースの構成->ビルド->デプロイ→キャッシュ永続化の一連の流れの合計時間の比較を行った。 過去に構成したビルド環境で運用中のプロジェクトと比較して、圧倒的に時間が短縮される結果が出た。 **過去に構成したビルド環境で運用中のプロジェクト** アセットのサイズ17GBに対して45分から60分程度 Unityのバージョンが古いことよるインポート時間が遅いという不利を含んではいる **今回構築した環境** アセットのサイズ40GBに対して20分程度 アセットのサイズ1.7GBに対して5分程度 ## 環境適応&障害復旧のコスト削減 外的環境の変化はたまにあるものの、構成要素がすべて適度な粒度で分けて管理されていて、切り分けて調査・対応ができるため、障害復旧は容易になった。 また、構成要素のバージョンアップで問題が起きた場合も、容易にバージョンを戻すことが可能になったため、各種バージョンアップのハードルが下がった。

2023年/2年以上

新規プロジェクト①・②の開発

# 次項記載の共通基盤に載せた上で共通基盤と同時に開発を進行 - 設計・コードレビュー - 開発ワークフローの構築・ルール化・資料化 - コンテンツ開発の設計 - アウトゲームの設計・実装 - Unity、各種SDK、パッケージのバージョンアップの取り込み - デザイナー、スクリプター向けのワークフロー・ツール開発やそのサポート

2024年/半年以内

TA(ネイティブエンジニアⅡ)セクションを設立

近年、ネイティブエンジニアの業務範囲が拡大しており、かつここの問題解決に必要な専門知識のレベルも上がっているため、ネイティブエンジニアとして全ての範囲の業務を求められるレベルでこなすことが難しいと感じていた。 ネイティブエンジニアをネイティブエンジニアⅠとネイティブエンジニアⅡ(一般的にはTAと呼ばれる職)に二分し、それぞれの業務範囲を限定しつつ専門性を上げることを狙っている。 また、それに伴いネイティブエンジニアのマネージャーから両セクションのマネージャーにポジションをシフト。 設立の経緯や今後の展望など、詳細はインタビュー記事で話している。 https://game8.jp/articles/568517

2022年/半年以内

技術戦略チーム立ち上げ

# 抱える問題  私は会社が抱える問題のうち、特に次の二つの問題に危機感を感じています。一つ目は、知識が個人に蓄えられ、組織に蓄えられない(知識の属人性が高い)という問題です。もう一つは、エンジニアをなかなか採用することができない(エンジニアが退職するリスクを軽減できない、将来使う技術の研究ができない)問題です。  この二つの問題から派生する様々な問題もありますが、この二つの問題の解決をすることがそれらの派生問題の解決にも繋がると思っています。 --- # 原因と派生する問題 この二つの問題が発生する原因と、そこから派生する問題についてまとめました。 --- ## 【問題1】知識が個人に蓄えられ、チームに蓄えられない ### 【問題1.1】プロジェクト間で知識や技術の共有が行われない #### 原因 - 口頭で共有する場もなければ、資料として共有するシステムもないため - 個人としては、共有するメリットよりもデメリットが大きいため - 主にプロジェクト軸で個人の評価が行われるため、他プロジェクトや会社に協力することが短期的な評価に繋がらないため - 評価されるためにはプロジェクトに注力することが一番の近道であり、他プロジェクトや会社に協力するよりも、プロジェクトの業務を優先するため - そもそも人員が足りておらずプロジェクトの業務で手一杯であり、そんなことをする余裕がないため #### 派生する問題 - 同じ失敗が隣のプロジェクトでも起きてしまう - 新規プロジェクトであるにも関わらず、技術的に後退する場合がある - 同じような機能の開発を繰り返す - 開発に向き合う際の観点がメンバーにのみ依存するため、考慮漏れが発生し、様々な問題を引き起こす - プロジェクト間で同じ要件を要求されているにも関わらず、メンバーによりその捉え方や捉える範囲が違うため、ソリューションが全く異なる場合がある - プロジェクトに依って同じ要件に対するソリューションが全く異なるため、開発・運用のフローやツールが異なり、より属人性が高まる ### 【問題1.2】ドキュメント化やマニュアル化が十分でない #### 原因 - 人員が足りておらず、とにかく目の前の業務をこなすだけで手一杯なため - ドキュメント化やマニュアル化されるべきだと考える情報や業務の基準が人によって違う #### 派生する問題 - プロジェクトの業務の引き継ぎコストが高く、新規メンバーを受け入れにくい - 担当するメンバーに依ってヒューマンエラーが発生しやすい - インシデント対応や確認のコストなどの運用コストが下がらない - 運用コストが下がらないため、新しい機能やイベントの開発や改善ができなかったり、制約がかかったりする --- ## 【問題2】エンジニアをなかなか採用することができない #### 原因 - 技術的な情報発信ができておらず、外のエンジニアから見て会社の良い点や悪い点、活躍できるエンジニア像がわからないため - どんな会社で、どんな開発をしているのか、いまいち分からないので興味が持てない - 「余裕を持った人員配置にした方がいい」という考えよりも、それを阻害する要因の方がプロジェクト・役員・人事にとって大きいので、優位性のある待遇のオファーを提示できないため - 現場の人員で最低限の運用は出来ている - 正社員で採用しミスマッチだった場合のリスクを取りたくない - 既存のエンジニアが退職した場合、どのような影響があるか理解していない(もしくは、理解しているが可能性やリスクを軽視している - ダイレクトリクルーティング、ヘッドハンティングなどのオファー型の採用媒体を利用しておらず、受け身な採用姿勢 #### 派生する問題 - エンジニアが退職するリスクを軽減できない - 将来使う技術の研究ができない - 新規プロジェクトを立ち上げられない - 運用プロジェクトに大規模な施策を入れることができない(売上を立て直す手段に制約がかかる) --- # 技術戦略チーム立ち上げのきっかけ  これまでプロジェクトの現場では、これらの派生する問題に対して局所的な対応を続けてきました。ただ、それでは根本的な解決には繋がらず、問題を先送りにしているだけに過ぎない、局所最適化は意味がなく、全体最適化が必要だと思いました。  私は、これらの派生する問題を洗い出し、抽象的な構造・類似点を見つけモデル化し、本当に解決すべき問題を【問題1】と【問題2】として捉えました。次に、それを文書化し(ちょうど上記のようなものです)、役員に解決の必要性と、その解決をするためには、この二つの問題に集中して立ち向かえるポジションとチームが必要であると訴えました。これをやらなけらば、主要なエンジニアが数人退職しただけで、プロジェクトも会社も存続が難しくなると伝えました。  このチームを「技術戦略チーム」と名づけ、その立ち上げに向けて準備を進めることになりました。 --- # チーム立ち上げまでの工程 #### 工程1  これまでの工程は、プロジェクト「ツイステッドワンダーランド」に所属しながら進めていました。当時(2021年後半)、プロジェクトのネイティブエンジニアは私1人であり、運用・開発の業務のかたわらで技術戦略チームの作業を進めていました。当然、プロジェクトから離れられる状況ではなかったです。  まずは私や技術戦略 に属して欲しいエンジニアがプロジェクトから離れられる状況を作らなければならなかったです。採用自体が課題ではあるのですが、プロジェクトの業務を引き継げるエンジニアを採用することがまずやるべきことでした。 #### 工程2  そのためには、今までの基準以上の金額をオファーで提示する必要がありました。それと同時に、社内の優秀なエンジニアの年収が、近しいスキルを持つ市場のエンジニアと比較して低いのではないかという問題がありました。市場にいる近しいスキルを持つ人に対してオファーで提示する金額が社内より高くなってしまう可能性があったのです。  それを是正するためには、エンジニアの市場価値をより正しく評価する指標が必要でした。社内に評価制度はありましたが、非常に抽象的で客観性に欠けていました。そこで私は、キャリアラダーを作成し、グレードごとに職責を明文化することで、評価指標の具体性と客観性を高めました。キャリアラダーを作成した理由や作成までの行程の詳細は「マネジメント対象」に記載しているので、そちらをご覧ください。 #### 工程3  当時は、ダイレクトリクルーティング、ヘッドハンティングなどのオファー型の採用媒体を利用しておらず、受け身な採用姿勢でありました。なので、「ここから1年間でエンジニアを最低でも10名は採用する」という目標を役員と人事に立ててもらい、オファー型の採用媒体を利用も始めてもらいました。  これは、【問題2】の原因の一つの解消に過ぎず、これでエンジニアの採用が今後全てうまくいくとは思っていません。採用候補の牌を広げるという意味では、一定の成果はありました。 #### 工程4  オファー型の採用により、半年ほどで数名のエンジニアを採用することができました。私のプロジェクトにも引き継ぎができるメンバーを受け入れることができ、プロジェクトの業務の引き継ぎを始めました。 #### 工程5  引き継ぎを進めながら、余裕ができてきたため、【問題1】と【問題2】に対する解決案を考えました。詳細は後述します。 #### 工程6  解決案の中には、プロジェクトに影響を及ぼすものもあるため、技術戦略チームとしてプロジェクトに干渉するためには、「どういうチームなのか」を説明し、理解してもらう必要が出てくるだろうと考えました。そこで、技術戦略チームの理念・ビジョンを考え、明文化することにしました。 ``` 理念:エンジニア全員が、個人ではなく組織で戦えるエンジニア集団の一員になる ビジョン:「SECIモデル」と「ダブル・ループ学習」の実践と推進をし、エンジニアの常識や文化のレベルを継続的に向上させる ```  また、解決案を計画的に実施していくためにマイルストーンも立てました。 --- # 解決案 ## 【問題1】「知識が個人に蓄えられ、組織に蓄えられない」に対する解決案 ### 【問題1.1】「プロジェクト間で知識や技術の共有が行われない」に対する解決案 - 口頭で共有する場もなければ、資料として共有するシステムもないため →インシデント共有会を定期的に開催し、口頭での共有の場を作る。 社内のドキュメントが集約されるようにConfluenceを全社的に導入した。共有されるべき情報がわかるように、プロジェクトの機能要件と非機能要件をそれぞれリストアップし、記事のテンプレートを作成する。プロジェクトのメンバーにはテンプレートに沿って記事を書いてもらう。 - 個人としては、共有するメリットよりもデメリットが大きいため - 主にプロジェクト軸で個人の評価が行われるため、他プロジェクトや会社に協力することが短期的な評価に繋がらないため - 評価されるためにはプロジェクトに注力することが一番の近道であり、他プロジェクトや会社に協力するよりも、プロジェクトの業務を優先するため →ナレッジ共有を全社的な貢献として評価軸に明記し、1on1や評価前に成果を振り返る。 - そもそも人員が足りておらずプロジェクトの業務で手一杯であり、そんなことをする余裕がないため →【問題2】の解決により、余裕のある人員配置にする。目安としては、全体の業務時間の3割はプロジェクトの開発・運用以外の業務ができるように。 ### 【問題1.2】「ドキュメント化やマニュアル化が十分でない」に対する解決案 - 人員が足りておらず、とにかく目の前の業務をこなすだけで手一杯なため →【問題2】の解決により、余裕のある人員配置にする。 - ドキュメント化やマニュアル化されるべきだと考える情報や業務の基準が人によって違う →私が作成したプロジェクトの運用マニュアルと同じ構成のマニュアルをプロジェクトごとに作ってもらう。「マニュアルを読めば、エンジニアメンバーであれば、誰でも運用ができる」レベルのものを基準とする。具体的な内容は次のようなもの。 - 開発の対象 - アプリの機能(一覧と概要) - 開発環境(非機能要件一覧と概要) - ドキュメント(各種ドキュメントへのリンク) - 更新の種類(機能追加・不具合修正・機能改善をする際の重要な観点) - 開発フロー - プロジェクト全体の開発フロー(フェーズごとの意思決定やどのような進め方をするかを考える際の重要な観点) - ネイティブチームの開発フロー(フェーズごとの意思決定やどのような進め方をするかを考える際の重要な観点) - 本番アプリの申請フロー - エラートラッキング(用途・利用するタイミング) - ツール・サービス(開発で利用するツールやサービスの用途・アカウント管理) - その他 ## 【問題2】「エンジニアをなかなか採用することができない」に対する解決案 - 技術的な情報発信ができておらず、外のエンジニアから見て会社の良い点や悪い点、活躍できるエンジニア像がわからないため - どんな会社で、どんな開発をしているのか、いまいち分からないので興味が持てない →人事と協力する技術広報チームを立ち上げ、テックブログを設立。社内向けのConfluenceの記事、ドキュメント、マニュアルの中から社外に公開できるものを定期的に選定し、社外向けに記事を編集し公開。 - 「余裕を持った人員配置にした方がいい」という考えよりも、それを阻害する要因の方がプロジェクト・役員・人事にとって大きいので、優位性のある待遇のオファーを提示できないため - 現場の人員で最低限の運用は出来ている - 正社員で採用しミスマッチだった場合のリスクを取りたくない - 既存のエンジニアが退職した場合、どのような影響があるか理解していない(もしくは、理解しているが可能性やリスクを軽視している - ダイレクトリクルーティング、ヘッドハンティングなどのオファー型の採用媒体を利用しておらず、受け身な採用姿勢 →「チーム立ち上げのきっかけ」、「チーム立ち上げまでの工程の工程2、3」で対応。

2018年/2年以上

ツイステッドワンダーランド

# プロジェクト概要  スマートフォンのゲームアプリ「ツイステッドワンダーランド」の開発・運用。 *** # 私が取り組んだ内容  プロジェクトの立ち上げ時から運用中の現在まで、ネイティブエンジニアリーダーとして設計・実装・テスト全てのフェーズを担当しています。 ## 具体的な業務内容 * アーキテクチャの設計から各機能の設計 * 開発環境の構築(要件別アプリのビルド・デプロイ、アセットをgitで複数ブランチ管理・ビルド・デプロイ・DLするシステム等) * 技術選定(ゲームエンジン、ミドルウェア、OSS、サードパーティ、ツール作成時の環境・言語等) * アプリ内で共通で利用する基盤機能の設計・実装・テスト(インターフェース設計、抽象クラス実装等) * アウトゲーム及びインゲームの設計・実装・テスト * 課金機能設計・実装 * アセットの要件定義・レギュレーション決定 * スクリプト作成ツール、アセットの要件チェックツールなど開発用ツールの設計・実装 * 機能の仕様のすり合わせ・提案・調整 * ネイティブチームのスケジュールとタスク管理、メンタリング * 設計・コードのレビュー * コードの見直し、リファクタ * 運用タスク(機能追加、改善、不具合修正等) * 運用マニュアルの作成 *** # 工夫点概要 - アプリとアセットのビルド&デプロイのシステムを開発初期に構築 - 属人化を継続的に減少させる - 技術的負債の返済 - 技術選定 - 機能要件の実現方法 - 演出基盤の共通化 - 運用の引き継ぎ *** ## 工夫点詳細 ### アプリとアセットのビルド&デプロイのシステムを開発初期に構築  プロジェクト初期の段階でアサインされたので、最初は開発環境を整備するところから始めました。開発メンバーが簡単にアセットのビルド&デプロイを回せる、ネイティブメンバーが簡単にアプリのビルド&デプロイを回せる環境を早期に作ることで、実装と検証のイテレーションを回せるようにしなければならないと考えました。  その際、アセットバンドルを利用するため、要件を洗い出し、アセットの命名やディレクトリ構成などのレギュレーションも決めました。  運用時に複数のイベントや機能を同時に開発することを考えて、アセットをブランチ別に管理・ビルド・デプロイ・ダウンロードする仕組みをその段階で提案し、実装しました。  アプリに関しては、当時手動でビルド&デプロイを実行していました。シェルスクリプトでそれらの処理を実装し、Jenkinsのジョブからそれを叩けるようにすることで簡単にビルド&デプロイするシステムを構築しました。 ### 属人化を継続的に減少させる  私が参加した時点で、すでにいた複数のメンバーによっていくつかのインゲームがすでに実装されている状態でした。プロトタイプの実装をそのまま引き継いでおり、設計思想が機能を担当するメンバー依存であり、属人性が高い問題がありました。  これでは私含め、今後入ってくるメンバーも含めて、引き継ぎのコストが高く開発に大きく支障が出ると思いました。幸いにもプロジェクトが始動してまだ数ヶ月でしたので、一度全ての機能を作り直す猶予がありました。  全ての機能の要件を洗い出し、共通の概念、機能を抽出し、インターフェース化、抽象化しました。具体的には、初期化、破棄、ロード、解放などをインターフェース化、シーンの制御、アセットのロード・リリース、サウンドの再生・ロード・リリース、コマンドによってオブジェクトを自由に制御する機能などのシステムです。  それらをゲーム共通基盤として実装し、既存の機能もそれに載せ替えて実装し直すことで、チーム全体の設計思想と実装を技術的に合わせることができました。  中には初期のデータ設計からやり直すべきものもあり、移行は大変でしたが、その甲斐あって複数の別の概念や機能として考えられていたものが統一されて実装され、我々エンジニアだけでなく、デザイナー・スクリプターの工数も大幅に削減できたと思います。 ### 技術選定  ネイティブに関わる技術選定はほぼすべて私が行いました。情報量、開発工数、サポート、安定性、今後もサービスが継続しそうか、特定の技術にロックインすることにならないかなどを考慮していました。 ### 技術的負債の返済  技術的負債を溜め込まないように、運用を見越して設計をしたり、定期的にコード全体を見直してリファクタしたりしていました。  また、コードレビューを設計・構造レベルで行い、機能要件を満たしていること、変更可能性を残してあることを担保するようにしました。 ### 機能要件の実現方法  プランナーやデザイナーが、実現手段ありきで要件を伝えてくることが起きていました。「既存の機能をこう組み合わせてこれを実現したい」といった感じで要件を出してくるのです。しかし、話の詳細をよく聞くと、以下のような問題がありました。 - 既存の機能を誤解しており、それでは実現できない。その方法を取るように既存機能を改修すると工数が大きくなりすぎる。 - そもそも本当に実現したいことを少し妥協して、簡易にできる手段だと思って提案してきている。  そこで、プランナーやデザイナーには「何を実現したいか」だけを集中して考えてもらい、「どう実現するか」はエンジニアが考えるようにしました。そうすることで、プランナーやデザイナーの想像力が抑制されず、本当に実現したいことを聞き出せると考えました。  本当に実現したいことの認識を合わせ、そこに共通点や抽象的な構造・類似点を見つけモデル化し、実装上の設計に落とし込むことで、この問題を解決できました。   ### 演出基盤の共通化  全てのインゲームで共通して利用できるコマンド機能を実装し、スクリプターが自由度高く演出を組みたてられる環境を用意しました。どのインゲームでも同じ概念、仕組みで動かせるのでこちらの開発工数もスクリプターの学習コストや運用工数を大幅に削減できたと思います。当然、スクリプトのツール類も複数開発する必要がなくなりました。 ### 運用の引き継ぎ  私が社内の技術横断組織を立ち上げるにあたって、本プロジェクトの運用業務を全てメンバーに引き継ぎました。運用にあたって必要な業務・開発の対象・開発フロー・チームメンタリング・重要な観点を構造的に文書化した運用マニュアルを作りました。そうすることで、無事運用業務全てを引き継ぐことができました。 *** # その他  このプロジェクトだけに対するものではないですが、以下の業務も並行して行っています。 * 新卒・中途ネイティエンジニアの採用面接と採用活動 * 新卒・後輩エンジニアの指導・1on1面談・キャリア相談 * 社内ゲームアプリの共通ネイティブライブラリの設計・実装 * 別新規プロジェクトの重要な技術的意思決定 * 技術横断組織の立ち上げ

2020年/1年以内

社内ゲームアプリの共通ネイティブライブラリ

# プロジェクトを横断して利用できる社内ライブラリの開発 プロジェクトに依存しないゲーム共通のレイヤーを設計・実装し、社内共通ライブラリとしてプロジェクト化しました。 Unityのパッケージマネージャーを利用して、モジュール単位でバージョン管理、リリース、インポートできる仕組みを設計・実装しています。 プロジェクトに依存しそうな部分は抽象化したり、実装をレイヤー分けして採用する側が自由にモジュールとして利用できるようにしてあります。 開発中の新規プロジェクトにて採用実績があります。 具体的には以下のようなモジュールを含みます。 * アセットのロードとリリース(カテゴリ別の参照カウンタでの管理) * アセットの差分ダウンロード * オブジェクトのプール * シーン管理と遷移 * カメラの制御 * タッチイベントの取得と制御 * アセットのプリロード * コマンドによる表示物やサウンドを高自由度で制御するシステム * 表示物の管理システム * サウンドの再生、管理、ロード、リリース * ServiceLocatorによる特定のクラスに限定されないグローバルアクセスポイント * Live2D, Spine, SparkGearのラッパー * WebViewのラッパー * ログ機能の拡張(タグ別に絞り込みやログファイルをエクスポートする機能など)

2016年/2年以上

マギアレコード

# プロジェクト概要  スマートフォンのゲームアプリ「マギアレコード」の開発・運用。   *** # 私が取り組んだ内容  プロジェクトの開発途中から参加し、リリース後は1年半ほど運用を行いました。後半の1年間はネイティブエンジニアリーダーとして働きました。 #### 具体的な業務内容 * 技術選定(ミドルウェア、OSS、サードパーティ、ツール作成時の環境・言語等) * アウトゲーム及びインゲームの設計・実装・テスト * スクリプト作成ツール、アセットの要件チェックツールなど開発用ツールの設計・実装 * 機能の仕様のすり合わせ・提案・調整 * ネイティブチームのスケジュールとタスク管理 * 設計・コードのレビュー * 運用タスク(機能追加、改善、不具合修正等) #### アプローチ・工夫  プロジェクトの開発中期に参加し、主にインゲームのアドベンチャーシーンの設計・実装とバトルシーンの 演出の設計・実装を担当しました。  前任者の設計が不十分でアプリの実装、スクリプトツールなど周辺ツールにおいて様々な追加要望に応えることができない状態であったため、設計を一から見直す必要がありました。 具体的には、スクリプトのパラメータ一つひとつが持つ機能や意味合いが大きすぎて、演出の自由度が大きく損なわれていました。 それを解消するために、複雑な意味のパラメータが増えていて今後もこれを拡張して運用していくのはエンジニア、スクリプター共にかなり辛い状況でした。 さらに、すでにスクリプトがかなり作られていてそれらの動作を担保する必要があったため、データ設計からやり直すことはできませんでした。  なので、追加の要望に答えられるようにまずは全体を設計・実装をし直し、既存の問題があるパラメータによる動作の実装は残しつつ、単一の機能となる新しいパラメータを新設することで、既存のスクリプトを動作させつつ、今後のスクリプトの演出の自由度を高めるようにしました。  また、スクリプト周辺ツールも設計・実装し直し、共通機能を抽象化して抜き出すことでパラメータの追加工数を大幅に削減しました。  iOSの証明書やProvisioningProfileの管理を手動で行っていましたが、その辺りの知識がないエンジニアや、非エンジニアにXcodeを利用してもらう場面があったため、CUIで簡単に証明書やProvisioningProfileの管理やインスールができるfastlaneを採用しました。 最初はその用途でしか利用していませんでしたが、ビルド周りの機能が豊富なため、アプリのビルドや配布、ストアへのアップロードなども含めてfastlaneで実装しました。  課金機能を実装しました。 Android、OSに対してJava、Objective-Cでそれぞれ実装し、cocos2dxにブリッジする必要がありました。cocos2dxの課金実装のレイヤーでOSでの差を意識したくないため、共通概念・機能を抽象化しました。  リリースして半年、入社して1年半経過した頃、全体の把握力、責任感、判断力などが評価されマギアレコードにおけるネイティブエンジニアのリーダーに採用されました。 *** # 次に活かせる学び  アドベンチャーシーンのスクリプトによる演出機能やバトルシーンの演出の実装を担当して、初期設計で要件・要望を大きく拾うことがいかに大事か学びました。 その場で出てきた要件通りに実装するだけでは未来の追加要望に応えられない可能性が高いことを学びました。 スクリプターやデザイナーの要望はその場では具体的な案であることが多いのですが、それをそのまま鵜呑みにしないで、抽象的に捉え自由度の高い機能を提供しておくべきだと学びました。 ひとつ一つの機能がシンプルで副作用がない状態で実装しておくべきだと学びました。 そのようにしておくことで、後に出てくる要望に追加対応なしで応えられる場合がほとんどだったり、スクリプタやデザイナーが思い描いた演出を自由に組み立てられます。    リーダーになってアプリ全体の仕組みを理解し、改修しながら運用することで、一見関係性のない機能にも共通性を見出し、インターフェース化や抽象化することの大事さを学びました。 凝集度を高めることによって、クラス間が疎結合になり、再利用性が高まり、不具合やコードを理解するコストを大幅に削減できます。 また、インターフェースに沿って実装を強制させたり、方針を技術的に縛ることが可能であり、この発想やそれを形にするスキルはプロジェクトの全体把握や複数人開発において非常に強力な武器だと感じています。 これらの学びは「マギアレコード」においては開発中期からの参加のため、十分に生かしきれませんでした。しかし、この経験から深く反省し、「ツイステッドワンダーランド」では存分に力と思いを発揮できたと思っています。

マネージメント能力

このマネージメント能力は公開されていません

このマネージメント能力は公開されていません

このマネージメント能力は公開されていません

アピール項目


アウトプット

GitHub アカウント
あり
Qiita アカウント
未入力です
Zenn アカウント
未入力です
Speaker Deck アカウント
未入力です
SlideShare アカウント
未入力です
特にアピールしたいアウトプット
未入力です

今後、身につけなければいけないと思っている技術は何ですか?

* 大規模開発のあり方 50〜70人程度(内エンジニア10人程度)の開発現場をエンジニアリーダーとして経験したことはある。 より大規模な開発を経験して、そのフローや分業体制を知りたい。 * AIを利用したゲーム開発 設計の壁打ち、パフォーマンスの最適化、普段あまり使用しない言語のコーディングなどでAIはよく利用している。 ChatGPT Codexを利用して、AIに直接開発をさせるフローを構築してみたい。

あなたが一番パフォーマンスを出せるのはどんな環境ですか?

* 評価に納得感がある(互いに期待値がうまく言語化され共有される) * ワーク・ライフ・バランスが尊重される * 私語は多くなく、生産的な会話が多い * 安心して対人リスクを取れる組織

キャラクター

直近で一番やりたいこと
サービスを作りたい
好きなスタイル
好きな規模
水とプログラミングどっちが大事?
自信を持って人より秀でていると言える点
学習能力 / 問題解決力 / 責任感
スキルのタイプ
得意なフェーズ
会社を選ぶ一番の基準
一緒に働く人
やりたくない分野
SI / 金融 / 医療・介護 / 人材 / 広告 / ファッション / アダルト / BtoB / 仮想通貨
その他の特徴
使用言語にはこだわらない / レガシーな環境を改善できる
その他のやりたいこと・やりたくないこと
未入力です

やりたい事

手を動かして設計してコードを書きたい
絶対やりたくない
あまりやりたくない
別に普通
やりたい
絶対やりたい
価値あるプロダクトを作り成長させたい
絶対やりたくない
あまりやりたくない
別に普通
やりたい
絶対やりたい
学び続けて技術力でプロダクトに貢献したい
絶対やりたくない
あまりやりたくない
別に普通
やりたい
絶対やりたい
意義があることや社会に貢献できる仕事がしたい
絶対やりたくない
あまりやりたくない
別に普通
やりたい
絶対やりたい
人や計画の調整・マネジメントをしたい
絶対やりたくない
あまりやりたくない
別に普通
やりたい
絶対やりたい
レガシーなシステムの保守・運用・改善をしたい
絶対やりたくない
あまりやりたくない
別に普通
やりたい
絶対やりたい
企画や仕様を考えるところから関わりたい
絶対やりたくない
あまりやりたくない
別に普通
やりたい
絶対やりたい
業務効率を改善して一緒に働く人のためになりたい
絶対やりたくない
あまりやりたくない
別に普通
やりたい
絶対やりたい
全社横断的な共通基盤作りや強化をしたい
絶対やりたくない
あまりやりたくない
別に普通
やりたい
絶対やりたい
組織や文化を作る・成長させる仕事をしたい
絶対やりたくない
あまりやりたくない
別に普通
やりたい
絶対やりたい

基本プロフィール

年齢
今年で30代中盤
好きなテキストエディタ
Rider
希望勤務地
東京都
希望年収
1100万円
ご意見箱

要望、不具合報告、使いづらい点や感想など、お気軽にお寄せください。
いただいたご意見は、今後のサービス向上に活用させていただきます。

なお、このフォームは受付専用のため、返信を行っておりません。
返信を希望する場合はお問い合わせよりご連絡ください。

  • {{error}}
転職ドラフトを友人や同僚に薦める可能性はどのくらいありますか?