コンテンツへスキップ

QualiArtsengineer blog

アルバイトとして、初めてのモバイルゲームのCIの仕組み作りに挑戦した話

アルバイトとして、初めてのモバイルゲームのCIの仕組み作りに挑戦した話

11 min read

はじめに

株式会社QualiArtsで6月〜7月の2ヶ月間、内定者アルバイトとして働いていました水溜です。

今回の記事はその期間の中で私が行ったGitHub Actionsを使用した固定テキストのデータ表示に関するCIの構築の話を紹介すると同時に2ヶ月間のモバイルゲーム開発を通して学んだことや感じたことなどを書いていきます。

これまでの開発経験

私は現在在籍している専門学校に入学してからゲーム開発を始めました。そしてこれまでに学内やインターン活動などでたくさんのゲーム開発を経験してきました。過去に就業型インターンに参加したこともあるのでモバイルゲームの会社での業務経験も1ヶ月ほどあります。 しかし、CIの構築のような非ゲーム領域の仕組み作りは今まで触れたことがありませんでした。 私は「学生間の開発では経験できなかったこと」や「幅広く知識を身につけること」に興味があったため、上司に相談したところ、「ゲーム内のラベルに関するCIの構築」というタスクをご提案いただきました。

CIの仕組みづくりの背景

今回挑戦したタスクはゲーム内の固定の文言に関するCIの構築でした。ゲーム内の固定の文言とは、たとえばゲーム内の「決定」「確認」「閉じる」といったユーザーに提示される文言のことです。 まずはそもそものゲーム内の仕組みの説明を交えながら、どのような経緯で今回のCIを作成したのかを紹介します。

QualiArtsではゲーム内の固定文言について、アプリに同梱されている文言データを読み込み、ゲーム内の場面に従って表示します。 文言データ内部では、表示する文言がKeyとValueのセットになっており、ゲーム側からは表示したい文言のKeyで問い合わせて表示したい文言を取得し、表示を行います。

加えて、この仕組みに合わせて、アプリの更新を行わなくともそれらの文言を運用しているマスタデータから上書きできるようにしています。これは、アプリ上の固定の文言データだけでは文言を更新するためにアプリを一度ビルドし直さないといけないからです。 マスタデータの仕組みによって、アプリ上のデータを変更して固定文言を表示するシンプルな仕組みを担保しつつも、運用による動的な文言変更が可能なシステムを実現しています。

しかし、上記のシステムにはひとつ課題がありました。それは、 マスタデータで変更した文言をアプリ上のデータに反映するフローを手動で行う必要がある という点でした。

例えば、不具合対応や仕様変更によってラベルのマスタデータに追加、修正が行われた場合、その後にアプリ上のデータに対して同じ変更を反映させなければいけません。 これでは手間もかかりますし、誤字脱字、更新漏れなどが発生する恐れがありました。

上記の課題を解消するために、定期的にアプリ上のデータを自動更新してくれるような仕組みを作る必要がありました。

Unity上で実行するバッチ処理の実装への挑戦

まずは、C#を使用してマスターデータ側のテキストデータとアプリ上のテキストデータを見比べてファイルを上書きする処理を作成しました。

// アプリ上のデータから文言データのKeyとValueをすべて取得する
var textData = TextManager.GetAll(x => x.key, x => x.value);

var lines = new List<string>();

// マスタデータを取ってくる処理がここには入る想定
// 本記事ではダミーの関数で示す
var masterTexts = Master.GetAllTexts();

// マスタデータとアプリ上のデータを比較する
foreach (var line in masterTexts)
{
    var key = line.Split(',')[0];

    // アプリデータ上のテキストに対して、マスタデータに定義されているkeyからValueの取得を試みる
    if (textData.TytGetValue(key, out var value))
    {
        // アプリデータ上のテキストデータに存在する改行コードを文字列に置換する
        value = value("\r\n", "\n").Replace("\n", "\\n");
        lines.Add($"{id}, {description}");
    }
    else
    {
        lines.Add(line);
        Debug.Log($"{key}に該当するvalueが見つからなかったため新たに{line}を追加";
    }
}

// マスタデータを上書きする処理がここには入る想定
// 本記事ではダミーの関数で示す
Master.SetAllTexts();

次に、Unityをバッチモードで起動して上記の処理を実行することに挑戦しました。

私は今までUnityをバッチモードで起動した経験がなく、コマンドラインの経験も浅かったのでバッチ処理を実装できるか不安でした。

ですが、プロジェクト内で既にバッチ処理を行うための基盤がありました。詳細な実装については秘匿情報もあるため省略しますが、SceneTaskRunnerというベースクラスとして定義されており、継承して実装するだけでバッチ処理を実現することが可能になっていました。

そこから、実際にSceneTaskRunnerというクラスを利用している他のコードを参考にすることで着実に実装を進めていくことができました。 また、この仕組みに則って実装を行うとUnityエディタ上で簡単に処理を実行できるようになっているため、自分で実装した処理の挙動を簡単に確認することができました。

シェルスクリプトを用いた実装への挑戦

次に、Unity上でバッチ処理ができる状態を実現したところで、その処理を自動で実行する仕組みづくりに挑戦しました。

QualiArtsではこういったバッチ処理にはGitHub Actionsを採用していることが多かったです。 GitHub Actionsに関する取り組みは過去のブログでも紹介されているので、興味があればそちらもご参照ください。

GitHub Actionsに関する記事一覧

この流れに則り、今回の処理もGitHub Actionsで実行する形をとりました。GitHub Actions内でUnityのバッチモードを起動する方法としてシェルスクリプトを採用しました。

ここでひとつGitHub Actionsに取り組む前に大きな課題がありました。それがシェルスクリプトの利用でした。 前述したように、私はコマンドラインでのコマンド実行経験は浅く、シェルスクリプトの知識はほぼゼロでした。 そこで、まずはコマンドの書き方や引数の渡し方などを勉強するところから始めていきました。

ある程度インプットしたところで、勉強と実装を並列で進めることにシフトしました。 Unityのコマンドライン引数を調べたり、参考文献と自分のコードを比較したりしながら処理を書いていきました。

慣れない引数の渡し方やコマンドの書き方などに少し苦戦しましたが、上司にサポートしてもらいながら、コマンドラインからUnityのバッチモード起動と自動更新処理の実行を行うことができました。

ここまでの過程によって、Unityのバッチモード起動、テキストデータの自動更新の実行を達成できました。 UnityのGUIを使用せず、コマンドの実行によって任意の処理を実行した経験が今までなかったため、とても達成感がありました。

GitHub Actionsへの挑戦

Unityのバッチモード起動と、テキストデータの自動更新の処理をコマンドラインで呼ぶことができたので次はGitHub Actionsから呼び出すことに挑みました。 GitHub Actionsはworkflowという単位でひとつの処理を記述します。 workflowはYAML構文を用いて記述する必要がありますが、私はYAMLを使用するのは初めてでした。そこで、GitHub Actionsについて勉強すると同時に、YAMLの書き方も勉強しながら実装していきました。

前述にもあるように、QualiArtsでは様々なCI/CDをGitHub Actionsを用いて構築しています。それらを参考にしたり、GitHub Actions ドキュメントを参考にし、学習と実装を行いました。

YAML構文に慣れないうちは不正な空白やインデントによってエラーを起こしてしまうことが多かったです。また、引数の定義方法や渡し方にもかなり苦戦しました。 発生したエラーの特定やそれに対するコードの修正も大変でしたが上司の手助けもあり、なんとなくでコードを修正するのではなくエラーが出る原因と修正内容を理解しながら実装を進めていくことができました。

何度目かのトライで最後まで処理が実行され、テキストデータのファイルに変更差分が発生したことを確認できました。

これでテキストデータの更新をGitHub Actionsから実行することはできました。 ですが、現状だと発生した変更差分のプルリクエストを作成する部分は手動で行う必要があります。そこまで自動化することでさらに工数の削減が測れるのではないかと考えました。 そのため、テキストデータ更新時に発生した変更差分のプルリクエストを自動で作成するよう、workflowに追記していきました。

プルリクエストの自動作成については、公開されているpeter-evans/create-pull-requestを使用しました。

usesに定義し、必要なデータを引数に渡すと簡単に実装できました。

実行し、「高速化のための処理」「Unityのバッチモード起動とテキストデータの自動更新処理」、そして最後に「プルリクエストの作成処理」という流れが実行されることが確認できました。

実際のworkflowを次に示します。

on: 
  workflow_dispatch:
    inputs:
      lock-wait-timeout:
        description: 'ライブラリのロックを待機する時間'
        required: false
        default: '600'
      project-path:
        description: 'プロジェクトパス'
        required: false
      target-platform:
        description: '実行するプラットフォーム(Android, iOS, ...)'
        required: false
      unity-args:
        description: 'projectPath以外のUnityに渡す引数'
        default: ''
        required: false
      api-dev:
        description: 'apiの接続先'
        required: false
      github-token:
        description: 'GitHubのトークン'
        required: false
        default: ''
      add-paths:
        description: 'コミット対象のファイルパス'
        required: false

jobs:
  synchronize-text:
    name: synchronize-text
    
    runs-on: [self-hosted, macos]
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          clean: true
          submodules: recursive
          persist-credentials: false
      - name: Execute Unity
        id: execute-unity
        uses: ./.github/quaunity-actions/unity-project
        with:
          project-path: ${{ inputs.project-path }}
          unity-args: |
            -buildTarget ${{ inputs.target-platform }} \
            -batchmode \
            -nographics \
            -silent-crashes \
            -logFile - \
            -executeMethod TaskRunnerUtility.RunTask -- TextUpdater "api=${{ inputs.api-dev }}" \
            -quit
      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v6
        with:
          commit-message: Update Text
          branch: "feature/update/text"
          title: Update
          body: |
            ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
          author: GitHub <noreply@github.com>
          add-paths: ${{ inputs.add-paths }}

今回学んだこと

今回のCIの構築を経験して私はGitHub Actionsやシェルスクリプトに興味を持ち、少しづつ勉強することにしました。 非ゲーム領域の開発をすることがより良いゲームの開発につながることを実感し、CIの構築にどこかでまた挑戦してみたいと思いました。

今回はCIの構築に挑戦したことを主に話しましたが、それ以外でも学んだことはたくさんありました。

この2カ月間、私は主にアウトゲーム部分のUIの開発を担当しました。 そのため、莫大なデータの中から必要なデータを正しく取得し、必要に応じて整理し、処理に流し込むといった流れをたくさん経験することができました。 また、作成したコードに対しては多くのレビューがつくので、そこから周りのエンジニアの方が普段意識していることや知らなかった文法などを多く学ぶことができました。

そのおかげで、端的なコードを書く意識がついたと同時に、データの取得やつなぎこみといった処理の勘所を掴めるようになり、以前よりもスムーズに実装ができるようになりました。

また、他セクションの方との連携方法やゲーム開発の流れ、運用方法など学生間では学ぶことのできない部分をかなり実践的に学ぶことができました。

今回感じたこと

学びだけでなく、感じたこともいくつかありましたので紹介します。

現場の熱量の高さ

入社してすぐに現場の熱量の高さを感じることができました。

エンジニアやデザイナー、プランナーなどの職種の垣根を超えて、とにかく質のいいゲームをユーザーさんに提供しようという熱量を肌で感じながら開発を行えました。この熱量はいい緊張感になりました。 いつまでも学生気分でいられないなと感じましたし、みなさんと同じ、もしくはそれ以上の熱量を持って取り組もうと入社してすぐに思いました。

また、多くの人が開発者でありながらそのゲームのユーザーであり、コアなファンでもありました。開発しているゲームに愛を持って、しっかりと遊ぶことは開発者として大事な要素だと思っているのでとてもいい現場だと感じました。

充実した開発基盤と開発経験

タスクを行っていくうちにQualiArtsには整った基盤と高い技術力があり、ゲーム開発のノウハウが豊富であることにも気づきました。

たとえば、QualiArtsではUI量産のための基盤が存在します。 ゲームを構成する様々なUI機能には共通部分があり、それらを都度実装するのには手間がかかってしまいます。 ですが、UI基盤を利用すればそれらの手間を省くことができるため、実装コスト削減に繋がります。

このような基盤を整えているおかげで、都度作成する必要があるものにコストを割かないで済みますし、プロジェクトを跨いで基盤を活用することで多くの工数削減につながります。

そして、その浮いた工数をゲームの面白さの追求やユーザーの声の反映といった部分に使うことでより質のいいゲームを目指す体制を実現しています。

終わりに 

今回の記事はCIの構築に挑戦した話を中心に、私が体験したこと、感じたことなどを記事にしました。 改めて、ゲーム開発を仕事にできることを嬉しく思いますし、現状に満足せずこれからもたくさん学んでいきたいと思っています。

この2ヶ月間は今回紹介したようなCIの構築なども含め、私の知らないことをたくさん学び、実践することができました。 そのおかげで、とても充実した楽しい時間になりました。

QualiArtsエンジニアブログ編集部です。