Slide 1

Slide 1 text

『ヘブンバーンズレッド』のリグレッション テストにおけるストーリーの自動プレイ 株式会社WFS エンジニア 菊地陽樹 株式会社WFS シニアQA 山木弘

Slide 2

Slide 2 text

菊地 陽樹 株式会社WFS エンジニア 2024年4月グリー株式会社(現:グリーホール ディングス株式会社)へ新卒入社。 株式会社WFSにて『魔法少女まどか☆マギカ Magia Exedra』のリリース業務に従事。その後、 2025年7月より『ヘブンバーンズレッド』の開発 プロジェクトに参画し、現在に至る。 2

Slide 3

Slide 3 text

山木 弘 株式会社WFS シニアQA 2012年4月2024年グリー株式会社(現:グリー ホールディングス株式会社)へ入社。 主に運用中のゲームタイトルにおいて、静的デー タの検証やモバイルアプリケーション検証の自動 化業務を担当している。 3

Slide 4

Slide 4 text

ヘブンバーンズレッド ジャンル: RPG リリース: 2022/2/10 配信端末: iOS, Android, Steam フィールドを駆け回り、 かけがえのない日常を体験しながら 命を賭した戦いに挑むドラマチックRPG 4

Slide 5

Slide 5 text

QA工数の肥大化 4年以上の運用による ストーリーや機能の増加 50種以上のイベントストーリー 様々な育成コンテンツ リグレッションテストのQA工数が肥大化 ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key 5

Slide 6

Slide 6 text

QA工数の肥大化への対策 リグレッションテスト 深刻な不具合がないことを確認 新機能ほど詳細な確認は不要 進行不能など、ゲームの進行に影響が出る 機能リリース時に詳細な QA は一度完了し 大きな不具合を確認 ている これらを満たす自動プレイにより解決が可能 6

Slide 7

Slide 7 text

自動プレイの概要 機能 ● 進行に必要な操作を自動で行い不具合がないか確認 ● Lua スクリプトを用いた自動プレイ(後ほど紹介) ○ WFSの他プロジェクトを参考に実装 対象 ● リグレッションテスト ● 第一歩としてイベントストーリーを自動化 7

Slide 8

Slide 8 text

ストーリーの仕様 1つのストーリーの中に、日付や時間帯が存在 ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key 8

Slide 9

Slide 9 text

ストーリーの進行(手動プレイ) ストーリーのリグレッションテスト ● それぞれの時間帯で、進行に必要な操作を順番に行い、進行不能などの エラーがないことを確認 Autoボタン押下でキャラクターの移動 Skipボタンを押下で会話をスキップ ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key 9

Slide 10

Slide 10 text

ストーリーの進行(自動プレイ) リグレッションテストの自動化 ● 進行に必要な操作を、順番に自動で実行することで正常に進行することを 確認 Autoボタン押下でキャラクターの移動 Skipボタンを押下で会話をスキップ ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key 10

Slide 11

Slide 11 text

実装要件 ● 開発当時の主な実装(機能)要件 本番への影響をなくす テストコードの 保守を容易にする 実機で動作可能にする エンジニアが基盤実装し、 QAがスクリプトの実装を可能にする 11

Slide 12

Slide 12 text

自動プレイの実装 12

Slide 13

Slide 13 text

環境 ● Unity 6.0 (6000.0.65f1) ● Lua 5.4.8 ● Tencent/xLua 2.1.16 ○ Unity で Lua を使用するためのライブラリ 13

Slide 14

Slide 14 text

Lua とは ● スクリプト言語 ○ ● エンジニア以外でも書きやすい言語 ヘブバンではアドベンチャーパートの実装に使用 ○ 「スクリプター」が実装 ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key 14

Slide 15

Slide 15 text

xLua の特徴 ● Lua → C# の呼び出し ○ ○ ● C# 側の static メソッドを Lua 側に公開可能 公開した static メソッドを文字列で呼び出す C# → Lua の呼び出し ○ LuaEnv(Lua Virtual Machine) を通して Lua を実行 static メソッドを実行 Lua C# Lua 文字列を実行 Lua ↔ C# 間で任意の処理を実行可能 LuaEnv 15

Slide 16

Slide 16 text

自動プレイの単一機能 ADVのスキップボタンを押下(0.5倍速の動画) ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key 16

Slide 17

Slide 17 text

自動プレイ単一機能の実装 ● ボタンのパスを受け取り、ボタンを押下する関数を C# 側で定義 ○ static メソッドで定義し、Lua 側に公開 public static void ClickButton(string path) { var button = FindObject(path); button.OnClick(); } ● Lua 側から関数を呼び出す CS.LuaMethod.ClickButton("UIAdv/SkipButton") C# static メソッドを実行 Lua 17

Slide 18

Slide 18 text

自動プレイの仕組み 自動プレイ用の LuaEnv(Lua Virtual Machine) を作成 ● 自動プレイ用の Lua のスクリプトを本番から呼び出せない ヘブバンアプリ用 LuaEnv 自動プレイ用 LuaEnv ヘブバンアプリ用の関数登録 自動プレイ用の関数登録 ADV の実装 ・ ゲーム全般の実装 1s 毎のループ処理 → ループ内で自動化処理 18

Slide 19

Slide 19 text

自動プレイの仕組み ● 『1秒ごとのループ処理』 と 『State ドリブンな UI 操作』 19

Slide 20

Slide 20 text

自動プレイの仕組み ● 『1秒ごとのループ処理』 と 『State ドリブンな UI 操作』 ループ if title 1秒ごとのループ処理(Coroutine) if story State ごとに処理を分岐 20

Slide 21

Slide 21 text

自動プレイの仕組み ● 『1秒ごとのループ処理』 と 『State ドリブンな UI 操作』 ループ Story Loop if title 汎用処理 Story 中のループ処理 if story if storyId ==“XX” 汎用処理に加え、 ストーリーごとに個別で条件分岐 if storyId ==“YY” 21

Slide 22

Slide 22 text

自動プレイの仕組み ● 『1秒ごとのループ処理』 と 『State ドリブンな UI 操作』 ループ Story Loop if title if story 汎用処理 ダイアログ OKクリック if storyId ==“XX” if storyId ==“YY” 全ストーリーに共通する処理は、 汎用処理として実行 22

Slide 23

Slide 23 text

自動プレイの仕組み ● 『1秒ごとのループ処理』 と 『State ドリブンな UI 操作』 ループ Story Loop if title 汎用処理 Story XX Loop 汎用処理 if story if storyId ==“XX” if day == “01” if storyId ==“YY” if time == “0930” 23

Slide 24

Slide 24 text

自動プレイの仕組み ● 『1秒ごとのループ処理』 と 『State ドリブンな UI 操作』 ループ Story Loop if title 汎用処理 Story XX Loop 汎用処理 if storyId ==“XX” if story ストーリー別のループ処理 if day == “01” ストーリー内の if storyId ==“YY” 日付や時間で個別実装 if time == “0930” 24

Slide 25

Slide 25 text

自動プレイの仕組み ● 1秒ごとのループ処理 と State ドリブンな UI 操作 ループ Story Loop if title 汎用処理 Story XX Loop 汎用処理 if story if storyId ==“XX” 限られた状況下で、 if storyId ==“YY” 自動プレイ単一機能の実行可能 if day == “01” スキップボタン クリック if time == “0930” 25

Slide 26

Slide 26 text

自動プレイの仕組み ● 1秒ごとのループ処理 と State ドリブンな UI 操作 ループ Story Loop if title 汎用処理 Story XX Loop 汎用処理 if story if storyId ==“XX” if day == “01” スキップボタン クリック if storyId ==“YY” if time == “0930” 26

Slide 27

Slide 27 text

実装 Loop = function() Auto.Loop 汎用的な処理 – その他Stateの処理 if Auto.state("title") then if state == “story” – タイトルので自動プレイ処理 end ストーリーでの汎用処理 if Auto.state("story") then Auto.storyLoop() end Coroutinue で if story == “storyId” 1秒ごとに呼ばれる ストーリーでの汎用処理 自動化プレイ用のループ関数 if story == “storyId” – その他Stateの処理 end 27

Slide 28

Slide 28 text

実装 Loop = function() Auto.Loop 汎用的な処理 – その他Stateの処理 if Auto.state("title") then if state == “story” – タイトルので自動プレイ処理 end ストーリーでの汎用処理 if Auto.state("story") then Auto.storyLoop() end Coroutinue で if story == “storyId” 1秒ごとに呼ばれる ストーリーでの汎用処理 自動化プレイ用のループ関数 if story == “storyId” – その他Stateの処理 end 28

Slide 29

Slide 29 text

実装 Loop Auto.storyLoop = function() – 汎用処理 汎用的な処理 for _, pair in ipairs(Auto.StoryDef) do if state == “story” if storyId == pair[1] then ストーリーでの汎用処理 if Auto.Story.isLoadedLua(pair[2]) then Auto[pair[1]].loop() else if story == “storyId” Auto.Story.loadLua(pair[2]) end Story用のループ関数 ストーリーでの汎用処理 StoryId に対応した、 個別ストーリーのループを実行 if story == “storyId” end end – 汎用処理 end 29

Slide 30

Slide 30 text

実装 Loop Event01.loop = function() if day == "01" then 汎用的な処理 if time == "0530" then CS.LuaMethod.ClickButton("UIAdv/SkipButton") if state == “story” end ストーリーでの汎用処理 if time == "0830" then -- 実装 end end if story == “storyId” ストーリーでの汎用処理 if story == “storyId” ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key if day == "02" then -- 実装 end end 1秒に呼ばれる イベントストーリー毎の関数 30

Slide 31

Slide 31 text

実装 Loop Event01.loop = function() if day == "01" then 汎用的な処理 if time == "0530" then CS.LuaMethod.ClickButton("UIAdv/SkipButton") if state == “story” end ストーリーでの汎用処理 if time == "0830" then -- 実装 end end if story == “storyId” ストーリーでの汎用処理 if story == “storyId”©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key if day == "02" then -- 実装 end end Day 毎に実装を分岐させる 31

Slide 32

Slide 32 text

実装 Loop Event01.loop = function() if day == "01" then 汎用的な処理 if time == "0530" then CS.LuaMethod.ClickButton("UIAdv/SkipButton") if state == “story” end ストーリーでの汎用処理 if time == "0830" then -- 実装 end end if story == “storyId” ストーリーでの汎用処理 if story == “storyId” ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key if day == "02" then -- 実装 end end 時間帯ごとに処理を分岐 32

Slide 33

Slide 33 text

実装 Loop Event01.loop = function() if day == "01" then 汎用的な処理 if time == "0530" then CS.LuaMethod.ClickButton("UIAdv/SkipButton") if state == “story” end ストーリーでの汎用処理 if time == "0830" then -- 実装 end end if day == "02" then -- 実装 end end if story == “storyId” ストーリーでの汎用処理 if story == “storyId”©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key 自動化の実装 スキップボタンの押下処理 33

Slide 34

Slide 34 text

Lua テストスクリプト開発 ● 自動プレイに必要なUI 操作の関数呼び出しはツールで対応 ○ ○ 「パスを含む関数呼び出し」をツールに表示しコピー&ペースト出来るツールを作成 パスの取り方はパスの深さ通りだけある。 ■ UIのPrefab のルートからのパスに固定 NGな例 CS.LuaMethod.ClickButton("UIRoot/UIAdv/SkipButton") CS.LuaMethod.ClickButton("SkipButton") OKな例(UIAdv prefabからのパスになっている) CS.LuaMethod.ClickButton("UIAdv/SkipButton") 34

Slide 35

Slide 35 text

Lua テストスクリプト開発 ● Lua はスクリプト言語 ○ ○ Unity を中断 → コンパイル → 再開 の手順を踏まずに、特定の Lua ファイルのみを再読 み込みし、処理を変更可能 エディタから入力した Lua 文字列の実行も可能にし、Lua の実行テスト自体も高速に行 えるようにした。 Lua 実装 Lua 再読込 動作確認 エディタを起動したまま実装が可能 35

Slide 36

Slide 36 text

Lua テストスクリプト開発 ● ストーリー毎のファイル分割 ループ if story Story Loop if storyId ==“XX” Story XX Loop if day == “01” if time == “0930” if storyId ==“YY” 36

Slide 37

Slide 37 text

Lua テストスクリプト開発 ● ストーリー毎のファイル分割 autoplay_XX.lua ループ if story Story Loop if storyId ==“XX” Story XX Loop if day == “01” if time == “0930” if storyId ==“YY” 37

Slide 38

Slide 38 text

Lua テストスクリプト開発 autoplay_XX.lua ● ストーリー毎のファイル分割 autoplay_YY.lua ループ if story Story Loop if storyId ==“XX” Story YY Loop if day == “03” if time == “1230” if storyId ==“YY” 38

Slide 39

Slide 39 text

Lua テストスクリプト開発 autoplay_XX.lua ● ストーリー毎のファイル分割 autoplay_YY.lua ループ if story Story Loop if storyId ==“XX” Story YY Loop if day == “03” if time == “1230” if storyId ==“YY” StoryIdとファイル名により、自動でロード 39

Slide 40

Slide 40 text

Lua テストスクリプト開発 ● ストーリー毎の実装を QA が担当 autoplay_XX.lua autoplay_YY.lua autoplay_ZZ.lua Story XX Loop Story YY Loop Story ZZ Loop if day == “00” if day == “00” if day == “00” if time == “00” if time == “00” if time == “00” 40

Slide 41

Slide 41 text

本番環境への影響考慮 ● C# の実装は本番ビルドに含めない ○ ● プリプロセッサディレクティブ(#IF ~ #ENDIF)で囲み、開発用ビルドのみで動作する ように Lua は本番ビルドのアセットバンドルビルドからファイルを除外 開発用ビルド 本番ビルド 本番用の実装 自動プレイ用の実装 参照 & 操作 41

Slide 42

Slide 42 text

テストスクリプトの開発 (QA側の対応) 42

Slide 43

Slide 43 text

QA側のテストスクリプト開発概要 ● 大まかな進行は自動プレイシステムが担当 ○ ○ ○ ○ ● ゲーム内Autoボタン押下によるオート移動・オートバトル ゲーム内Skipボタン押下による会話のスキップ 会話中に現れる単純な選択 リザルト画面のスキップ システムがカバーしていない部分をスクリプト実装により自動化 ○ ○ ○ ○ シーケンシャルな処理が必要なメニュー 正解を選ぶ必要のある選択 ゲーム内Autoボタンが無効なシーンでの移動 ゲーム内Autoボタンが無効なバトル 自動プレイシステムとスクリプト実装による完全自動化 43

Slide 44

Slide 44 text

QA側のテストスクリプト開発 ● 大きく分けて3つのパートをスクリプト実装により自動化 ○ ○ ○ アドベンチャー ■ チュートリアル ■ 正解を選ぶ必要のある選択 フィールド ■ 他のシーンへの遷移操作 ■ イベントトリガーへのタッチ(キャラクター、アイテム等) ■ ゲーム内Auto機能が無効な場合の移動 バトル ■ とにかくバトルで勝利する 44

Slide 45

Slide 45 text

テストスクリプト実装(アドベンチャー) ● チュートリアル ここに動画入れる ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key 45

Slide 46

Slide 46 text

テストスクリプト実装(アドベンチャー) ● 正解を選ぶ必要のある選択 ここに動画入れる ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key 46

Slide 47

Slide 47 text

テストスクリプト実装(フィールド) ● シーンの遷移 ここに動画入れる ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key 47

Slide 48

Slide 48 text

テストスクリプト実装(フィールド) ● イベントトリガーへのタッチ、移動 ここに動画入れる ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key 48

Slide 49

Slide 49 text

テストスクリプト実装(バトル) ● とにかく勝てるように操作する ここに動画入れる ©WFS Developed by WRIGHT FLYER STUDIOS ©VISUAL ARTS/Key 49

Slide 50

Slide 50 text

自動プレイシステムを使用した結果 ● サードパーティー製ツールと比較して ○ ○ ● メリット ■ タイムボックスの概念を利用してホワイトボックス的なテスト実装が可能 ● テスト条件が事前に絞られているためコードに余計な判断や分岐が不要 ● デバッグ機能の時間遡行(任意のシーンへジャンプ)を併用することで高速な実装を実現 ○ ひと月でほぼ全てのイベントの自動化(45個中44個) デメリット ■ コンポーネント操作用のAPIが存在しない場合のブロッカー ● 代替できる処理がない場合手動での操作が必要 ○ サードパーティー製ツールと併用により改善可能 自動プレイにより手動での検証工数を約40%削減 ○ 進行不能な不具合を自動プレイシステムで検知可能にしたことで ■ ユーザーのみなさまにいつでも安心してプレイできる環境を用意 ■ ヘブバンの信頼性と品質向上に貢献 50

Slide 51

Slide 51 text

まとめ ● ● 自動プレイの実装により、検証工数の40%削減 機能要件も達成した状態での実装 本番への影響をなくす アセバン削除や #IF による対応 実機で動作可能にする QA用ビルドには実装を含めた テストコードの 保守を容易にする State ドリブンな設計で達成 エンジニアが基盤実装し、 QAがスクリプトの実装を可能にする QA が Luaスクリプトを実装 51

Slide 52

Slide 52 text

ご清聴ありがとうございました 52

Slide 53

Slide 53 text

No content