2K Views
November 13, 26
スライド概要
2026年10月にカプコンが開催した「CAPCOM オープンカンファレンス RE:2026」の講演資料です。
■ 講演映像(再生リスト)
https://www.youtube.com/watch?v=cnnrdXsipQk&list=PLD_N6X6tKlCM
株式会社カプコンが誇るゲームエンジン「RE ENGINE」を開発している技術研究統括によるカプコン公式アカウントです。 これまでの技術カンファレンスなどで行った講演資料を公開しています。 【CAPCOM オープンカンファレンス プロフェッショナル RE:2023】 https://www.capcom-games.com/coc/2023/ 【CAPCOM オープンカンファレンス RE:2022】 https://www.capcom.co.jp/RE2022/ 【CAPCOM オープンカンファレンス RE:2019】 http://www.capcom.co.jp/RE2019/
RE:Runtime RE:Dox RE:Launcher RE:UI RE:Profiler RE:Log 次世代に向け RE ENGINEをさらに進化させる 「REX」プロジェクトの 展望と未来 石田 智史 RE:AssetStream RE:Flows
RE:Runtime RE:Dox RE:Launcher RE:UI RE:Profiler RE:Log 次世代に向け RE ENGINEをさらに進化させる 「REX」プロジェクトの 展望と未来 石田 智史 RE:AssetStream RE:Flows このセッションでは、次世代に向けたRE ENGINEをさらに進化させる「REX」プロジェクトの展望と未来と題しまして、REXプロジェクトの概要を紹介します。 1
2 まず初めにREXプロジェクトとは何かについて紹介します 2
REXプロジェクト RE ENGINEとは カプコンの内製ゲームエンジン 2014.4に開発を開始 2017以降、一つの共通ゲームエンジンで高品質な27タイトル以上をリリース 2017 2019 2020 2021 2022 2023 2024 2025 2026 3 RE ENGINEはカプコンの内製ゲームエンジンです。 2014年4月にゲームエンジンの開発を開始し、2017年にRE ENGINEを使って作られたバイオハザード7がリリースされました。 それ以降、多くのカプコンのゲームタイトルがRE ENGINEを使って作られており、すでに高品質な27タイトル以上が発売されています。 3
REXプロジェクト RE ENGINEが抱える課題 プロジェクト規模の拡大 ゲームコードとアセット数の増加による イテレーション速度の低下 エンジン利用者の増加 市販ゲームエンジンと同水準の ユーザビリティへの要求の高まり AI技術の急速な発展 AIとシームレスに連携できるゲーム開発環境の 整備が急務 4 RE ENGINEを10年以上運用してきて、様々な課題が見えてきました。 一つは、プロジェクト規模の拡大です。 最初のタイトルのバイオハザード7と比べて、最近のタイトルではアセット数やゲームコードの規模が数倍以上になっています。 そのためアセットの取得、エディタの操作、スクリプトのビルドなどが遅くなりイテレーション速度が低下してきました。 次に、エンジン利用者数の増加です。 現在、カプコンのほぼ全ての開発者がRE ENGINEを利用しており、アクティブユーザーは2000人を超えます。 日本語圏以外の人材や、市販エンジン経験者も多く、ローカライズやエディタの使いやすさなどユーザビリティへの要求が高まってきました。 そして、昨今のAI技術の急速な発展です。 AIエージェントと連携したワークフローを整えることが、開発を効率化するための必須の条件になってきました。 4
REXプロジェクト RE ENGINEを次世代の水準へ REX : RE neXt ENGINE カプコンの次世代ゲームエンジンの 開発コードネーム RE ENGINEのレガシーを 新技術 (RE+X) で段階的に置き換え、 次世代の水準に引き上げる RE:Runtime RE:Dox RE:Launcher RE:UI RE:Profiler RE:Log RE:AssetStream RE:Flows 5 このような現在のRE ENGINEが抱える課題を解消するために、REXプロジェクトを立ち上げました。 REXプロジェクトは、ゼロから全く新しい次世代ゲームエンジンを開発するのではなく、 RE:Launcher RE:Profiler RE:AssetStream RE:Dox RE:UI RE:Log RE:Flows RE:Runtimeといった REプラスXの新しい技術をRE ENGINEに段階的に導入し、RE ENGINEを次世代水準のゲームエンジンに引き上げていくプロジェクトです。 5
6 それでは、REプラスXの新しい技術について、それぞれの概要を簡単に紹介します。 6
RE+X 概要 REXを構成するキーコンポーネント RE+X RE:Dox データ表現とシリアライズ基盤 RE:UI 仮想化されたツールUI基盤 RE:Log ログ/テレメトリ/RPC通信の統一基盤 RE:Flows ユーザースクリプト基盤 RE:Runtime 次世代のゲームランタイム基盤 7 今回紹介するのは、REXを構成するキーコンポーネントのうち、RE:Dox RE:UI RE:Log RE:Flows RE:Runtimeです。 RE:UI RE:Logなどは、この後の別のセッションでも詳細に紹介しますので、興味のある方は見ていただければと思います。 7
8 まず初めに、データ表現とシリアライズ基盤であるRE:Doxについて紹介します。 8
RE+X 概要 RE:Dox RE ENGINEのレガシー 様々なシリアライザ/DOMが混在 RuntimeSerializer,DataContractJson,DynamicJson,MessagePack,XmlSerializer … シリアライズされるプロパティや属性定義などの仕様がバラバラ 独自にハードコードされたパース処理が散在 INI/CSV … フォーマットが簡単な分、カンマやグループの扱い等、対応抜けも多い HTML … AngleSharp等の巨大なHTMLライブラリを入れるほどではない アセットの読み込みやコンバートが遅い 標準ライブラリで型情報を保存できるDataContractJsonSerializerが多用 9 RE ENGINEでは様々な要件に対応するために、様々なシリアライザとDOMライブラリが利用されていました。 問題は、それぞれのライブラリごとに、それぞれのルールがあり、属性の定義やシリアライズの結果が異なることです。 結果として、似たようなクラスやコンバーター、自動生成されたコードなどが溢れコードベースの複雑化に繋がりました。 また、INIやCSVなど簡単なフォーマットへの対応や、HTML内のちょっとした抽出などのために、独自のパース処理が散在していました。 これらは簡単な処理と言えども、カンマやグループ、エスケープ文字の扱い等が抜けていることも多くありました。 そして、RE ENGINEでは標準ライブラリで型情報が保存できる唯一の選択肢ということもあって、C#のDataContractJsonSerializerが多用されており、これ がパフォーマンス上の大きなボトルネックになっていました。 9
RE+X 概要 RE:Dox RE:Doxによるソリューション 様々なフォーマットを共通の軽量DOMとして表現 Json/Json5/MessagePack/CBOR/INI/CSV/HTML/XML/TOML… 共通DOMを同じAPIで操作 共通DOMを介してシリアライズ、デシリアライズを行う 高い汎用性と最高水準のパフォーマンス/メモリ効率 データの保存、編集、通信などあらゆるアプリケーションの中核になる 速度 型情報 メモリ効率 DOM編集 カスタム変換 Json以外 RE:Dox 〇 〇 〇 〇 〇 〇 DataContractJson × 〇 × × × × SystemTextJson 〇 △ ◎ / Node:△ 〇 〇 × NewtonsoftJson △ ◎ △ ◎ ◎ △ ※社内での主観評価 10 これらの問題を解消するために、データ表現とシリアライズ基盤としてRE:Doxを設計しました。 RE:Doxでは、Jsonを始めとして、MessagePack,Cbor,Xml,Html,Csv,Ini,Tomlなど様々なフォーマットを共通の軽量なDOMとして表現します。 これによって、様々なフォーマットを共通のAPIで操作することができ、共通のDOMを介してシリアライズ、デシリアライズを行うことができます。 またRE:Doxは他のC#の標準的なJsonシリアライザと比較しても、高い汎用性とパフォーマンスを両立することができます。 これによって、データの保存、編集、通信などあらゆる用途をRE:Doxで統一することが可能になります。 10
RE+X 概要 RE:Dox RE:Doxのキーアイデア 全フォーマットを共通のJson互換DOM+Triviaとして表現 64bitトークン配列+RAWデータとして軽量DOMを構築 DOMの変更を拡張トークンとして保持 ゼロコストDOM構築を可能にするDOXフォーマット マルチコアにスケールする2-Passデシリアライズ 11 それではこれらを実現するためのRE:Doxの核となるキーアイデアを紹介します。 11
RE+X 概要 RE:Dox 全フォーマットを共通のJson互換DOM+Triviaとして表現 ロスレスでDOMとの相互変換が可能 Trivia Comment Whitespace Tag Separator HTML5 INI 12 RE:Doxでは、Jsonを始めとして、HTMLやINIといったフォーマットを、共通のJson互換DOMと、コメント、空白、タグといった付加情報のトリビアとして表現しま す。 RE:DoxのJson互換DOMは、配列、文字列といった通常のJson要素型だけでなく、バイナリや16進数のような拡張タイプ情報も保持できます。 一般的なフォーマットのJsonへの変換との違いは、フォーマット固有の情報を拡張タイプやトリビアとして保持するので、ロスレスでDOMとの間を相互に変換するこ とが可能な点です。 12
RE+X 概要 RE:Dox 64bitトークン配列+RAWデータとして軽量DOMを構築 64bitトークン構造 DOM構造 (64bitトークン配列 + Raw Data) Raw Data 13 RE:DoxのDOMは、生のデータをパースして、64bitのトークン配列を構築します。 64bitトークンは、配列、整数などのタイプ情報と、ペイロードから構成されます。 ペイロードの仕様は、それぞれのフォーマットごとに自由に定義できますが、例えばJsonフォーマットでは、文字列タイプなら生データへのオフセットと⾧さを入れること が一般的です。 このような一般的にはテープ構造とよばれるDOM構造によって、メモリ効率の良い軽量なDOMを高速に作成することができます。 13
RE+X 概要 RE:Dox DOMの変更を拡張トークンとして保持 書き換えたトークンは最上位ビットを1にし、拡張テーブルのインデックスを設定 Raw Data Raw Data : 14 今までの一般的なテープ構造のDOMは、一旦、DOMを構築した後、編集することができませんでした。 RE:Doxでは、トークンの最上位の1ビットを拡張フラグとし、DOMの書き換えを行うと、拡張フラグを1に変更します。 そして、拡張フラグが1の時は、拡張トークンとして扱い、データを生データからではなく、拡張データテーブルから取得します。 このようにして、軽量なテープ構造のDOMを維持しながら、DOMの自由な編集を可能にしています。 14
RE+X 概要 RE:Dox ゼロコストDOM構築を可能にするDOXフォーマット DOXフォーマット ヘッダ / データ領域 / 64bitトークン配列 / フッタから構成 メモリ上にロードし、64bitトークン配列の先頭アドレスを指定するだけでDOMが構築 Dox Format Head Data 64bit Token[n] Foot 15 RE:Doxでは、64bitトークン配列と生データを直接保存したDOXフォーマットを利用することができます。 DOXフォーマットは、データをメモリ上にロードした後、先頭アドレスを指定するだけで、ほぼゼロのコストで高速にDOMを構築することができます。 15
RE+X 概要 RE:Dox マルチコアにスケールする2-Passデシリアライズ 軽量DOMを構築してからデシリアライズを行う2パス処理 デシリアライズ前にデータ構造や配列の要素数がわかる 一定要素数を超える配列はマルチコアで分散して 並列デシリアライズを行う twitter.json 最速 0.5949 ms canada.json 最速 7.208 ms citm_catalog.json 最速 1.621 ms REDox 1.0915 REDox 9.253 REDox 2.186 REDox Parallel 0.5949 REDox Parallel 7.208 REDox Parallel 1.621 STJ 1.4055 STJ 15.269 STJ 3.709 1.4128 Utf8Json 15.564 Utf8Json Utf8Json 0 1.41 0 15.6 2.575 0 3.7 BenchmarkDotNet v0.15.8, Windows 11 (10.0.26100.9106/24H2/2024Update/HudsonValley) AMD Ryzen Threadripper PRO 5975WX 32-Cores 3.60GHz, 1 CPU, 64 logical and 32 physical cores .NET SDK 10.0.303 16 標準的なJsonシリアライザはJsonをパースしながらデシリアライズを行う1パスの構造が一般的ですが、 RE:Doxでは、一旦軽量DOMを構築してから、デシリアライズを行う、2パスの構造になります。 2パスの構造にすることで、デシリアライズ前にデータの構造や配列の要素数がわかるため、効率的なデシリアライズが可能です。 また、2パスならではの自動的な並列デシリアライズも可能です。 C#で最速クラスとされるSystem.Text.Jsonや、Utf8Jsonと比較しても、RE:Doxは高速にデシリアライズを行うことができます。 16
仮想化された 17 次に、仮想化されたツールのUI基盤である、RE:UIについて紹介します。 17
RE+X 概要 RE:UI RE ENGINEのレガシー ツールの動作が重い、頻繁に固まる WPFの深くネストしたUI階層とレイアウト計算 大量の更新通知とUIスレッド制限で処理がストール 大量のメモリ消費とGCによる停止 WPF/XAML/MVVMの複雑な実装と管理コスト 大量のボイラープレートコード 大規模な XAML の管理、分割、リファクタリングが困難 プラットフォームとテストの制約 自動テストの作成、保守、実行コストが高い WindowsPCでしかツールが動作しない 18 RE ENGINEのツールは、C#のWPFというフレームワークを利用して作成されていました。 WPFは⾧い歴史と信頼性があり、機能も充実していて、広く使われているフレームワークですが、 大規模なツールになっていくと、様々な問題が出てきました。 一つは、ツールの動作が重く頻繁に固まるということです。 これはWPFの構造がそもそも冗⾧なのもありますが、大規模になると大量の更新通知がUIスレッド制限で詰まったりメモリ消費の多さからGCによる停止が頻発し ます。 また大規模なツールではMVVMによってNotifyPropertyChangedなどの定型コードの量が膨大になったり、XAMLの管理コストが飛躍的に上昇します。 他にもテストの保守コストが高かったり、MacなどのWindows以外のプラットフォームで動作しないという問題もあります。 18
RE+X 概要 RE:UI RE:UIによるソリューション 純粋なC#コードのみでUIを構築 Backend(WPF/MAUI…) XAMLを廃止しC#コードのみでUI構造を定義 プラットフォーム依存の描画と入力を抽象化 Input ビュー駆動アーキテクチャの導入 MVVM/イベント駆動/UIスレッドの概念を廃止 大量のUI要素を最小限のメモリで管理 RE:UI 仮想化とマルチバックエンド対応 WPF/MAUIなど複数のバックエンド上で動作 仮想デバイス上でのリプレイと単体テストの実行 Draw Backend (WPF/MAUI…) 19 これらの問題を解消するために、仮想化されたツールUI基盤としてRE:UIを作成しました。 RE:UIではXAMLを使用せず、純粋なC#コードのみでUI構造を定義します。 そして一般的なイベント駆動ではなく、ビュー駆動のアーキテクチャを導入します。 これにより、UIスレッドに制限されず、大量のUI要素を最小限のメモリで管理できるようになります。 そしてRE:UIは純粋なロジックのみで構成されているので、デバイスに依存しない決定論的なリプレイが可能です。 プラットフォームに依存する入力と描画はWPF/MAUIなどのバックエンドライブラリを任意に選択できます。 19
RE+X 概要 RE:UI RE:UIのキーアイデア UI要素と、UIインスタンスの分離 固定サイズのUIインスタンス MVVCアーキテクチャ View駆動モデルと中間描画コマンド マルチバックエンドと仮想化 20 それではこれらを実現するためのRE:UIの核となるキーアイデアを紹介します。 20
RE+X 概要 RE:UI UI要素とUIインスタンスの分離 UI要素は不変で一切の状態を持たない 一般的なUIフレームワーク (UIインスタンス = UI要素) CollectionView Grid Mutable RE:UI (UIインスタンス != UI要素) Mutable UIInstance UIInstance CheckBox UIInstance Label UIInstance Grid UIInstance Label UIInstance CheckBox Label CollectionView Grid UIInstance CheckBox Grid Immutable UIInstance CheckBox Label UIInstance UIInstance 21 一般的なUIフレームワークでは、UIインスタンスとUI要素は同じで、それぞれのUI要素が状態を持っています。 RE:UIでは一般的なUIフレームワークとは異なり、UI要素とUIインスタンスを分離しています。 そしてUI要素は不変で一切の状態を持ちません。 例えば、チェックボックスが複数あったとしても、RE:UIではチェックボックスのUI要素は一つで、それを複数のUIインスタンスが共有しています。 また、チェックボックスがチェックされているといった状態を、チェックボックス要素自体は持ちません。 21
RE+X 概要 RE:UI 固定サイズのUIインスタンス Instance Pool UIインスタンスは固定サイズ 必要最小限の共通フィールドで構成 親子階層、UI要素への参照など 構造体配列として事前にプール UIInstance UIInstance UIInstance UIInstance UIEntityがUIインスタンスを指す UIInstance 64bit(Index + Version) 超高速のUI生成・破棄 メモリの確保、解放なしでGCフリー UIInstance UIEntity 1:0 UIEntity 4:0 UIInstance UIInstance UIInstance 22 RE:UIのUIインスタンスは、UI要素の種類に関係なく、同じ固定サイズです。 UIインスタンスは、親子階層情報や、UI要素への参照など最小限のフィールドから構成されます。 そして、UIインスタンスは構造体配列として事前にプールされています。 UIインスタンスは、IndexとVersionから構成される64bitのUIEntityとして参照します。 この構造により、UIインスタンスの生成と破棄は、メモリの確保、解放なしに超高速に行うことが可能です。 また、メモリの確保、解放がないのでGCも発生しません。 22
RE+X 概要 RE:UI MVVCアーキテクチャ Model/View/ViewController ViewControllerがViewとModel間のデータを一方向で動的に中継する MVVMのようにModel/View/ViewModel間で双方向に変更を通知することはない UIスレッドの制限の撤廃 状態を持たないビューと一方向のデータフローによりユーザーの責任範囲で制御可能 MVVM Model MVVC Model View Model Notify Get/Set View Binding View Controller Get/Set View 23 RE:UIでは、一般的な、Model View ViewModelのMVVMとは異なり、Model View ViewControllerのMVVCというアーキテクチャを採用しています。 MVVMとの大きな違いは、データの流れがViewからModelへの一方向のみで、MVVMのようにModelからViewにデータの変更を通知するという流れが存在し ないことです。 一般的なUIフレームワークでは、UIへの通知は一つのUIスレッド内で行う必要があるというUIスレッド制限がありますが、 MVVCアーキテクチャでは、UIスレッドの制限がありません。 これは、ビューが状態を持たず、データがViewからModelへの一方向の流れのみなことで、UIスレッドの制限がなくても、ユーザーの責任範囲でレースコンディショ ンをコントロールできるからです。 23
RE+X 概要 RE:UI MVVCアーキテクチャ Viewの実装 static readonlyのC#フィールドとして定義 Viewは状態を持たない ViewControllerの実装 IUIControllerインターフェイスを継承 Get/SetBindingPropertyを実装 Viewからのプロパティ要求 対応するModel内のデータを適時取得変換して返す 24 具体的なMVVCアーキテクチャの実装例です。 まず、Viewは状態を持たず共有ができるため、static readonlyのC#フィールドとして定義されます。 そして、ViewControllerはGetBindingPropertyとSetBindingPropertyの2つのAPIを持つ、インターフェイスを実装します。 Viewからプロパティが取得、設定されたときに、都度、Model内のデータを取得したり設定したりします。 このように、MVVCアーキテクチャは、staticなViewと、Get/Setの2つのViewControllerのAPIという非常にシンプルな構成だけで成立します。 24
RE+X 概要 RE:UI View駆動モデルと中間描画コマンド View駆動モデル 可視状態のUI側が毎フレームModelのデータを取得し中間描画コマンドを作成 中間描画コマンドのレンダリング 前フレームの中間描画コマンドと比較し、差分のみをレンダリング 適応フレームレート 描画差分が一定期間ない場合は段階的に更新頻度が落とされる CheckBox CheckBox CheckBox CheckBox CheckBox : ListView OnDraw FillRect DrawText Renderer FillRect ViewController DrawText FillRect Model DrawText : 中間描画コマンド 25 RE:UIでは、UIフレームワークで一般的なイベント駆動モデルではなく、ビュー駆動モデルを採用しています。 ビュー駆動モデルでは、表示されているUI側が定期的に、Modelのデータを取得し、中間描画コマンドを作成します。 そして、前のフレームの中間描画コマンドと比較して、差分があった場合だけ実際のレンダリングを行います。 また、一定期間、描画の差分がない場合は、段階的に更新頻度が落とされます。 このように、ビュー駆動モデルでは、ビュー側が定期的にモデルの状態を取得することで、モデルからの更新通知を不要にしています。 そして中間描画コマンドによる差分比較と、適応フレームレートによって、静止していることが多いツールのパフォーマンスを改善します。 25
RE+X 概要 RE:UI マルチバックエンドと仮想化 バックエンドはWPF/MAUIから選択可能 WPFバックエンド 中間描画コマンドからのレンダリング、 ウインドウ生成、デバイスからの入力 RE:UIをWPF/MAUIコントロールとして 既存ツールに組み込み可能 既存コードベースからの段階的な移行 MAUIバックエンド 仮想バックエンドによるUIのリプレイと自動テスト 物理デバイスなしに確定的なリプレイと 中間描画コマンド結果の一致テストが可能 26 RE:UI自体は、ネイティブのデバイスからの入力と描画を行う機能を持ちません。 その代わり、バックエンドに既存のUIフレームワークを利用します。 例えばWPFバックエンドを採用した場合、WPFのコントロールの一部としてRE:UIを組み込んだり、RE:UIのコントロールの一部にWPFのコントロールを利用する ことが可能です。 これにより、WPFで作られたツールを段階的にRE:UIに置き換えていくことができます。 現在、RE:UIは、WPFとMAUIのバックエンドをサポートしており、どちらのバックエンドでも同じRE:UIのツールを動かすことができます。 また、バックエンドなしで動かすと、確定的なリプレイと中間描画コマンド結果の一致テストを行うことができます。 これにより、今までのUIフレームワークでは難しかった高速なUIのユニットテストが可能になります。 26
27 次に、ログとテレメトリおよびRPC通信の統一基盤であるRE:Logについて紹介します。 27
RE+X 概要 RE:Log RE ENGINEのレガシー ログ出力と管理の問題 ログの出力処理が重い、大量に出すとパフォーマンスに影響 ログを適切に分類・表示できない、重要度や対応方法をユーザーが判断できない ログ内容の変更にコード修正とレビュー・マージが必要でコストが高い トラブル発生時に必要なログが既に消えていることも多い テレメトリ通信の問題 送信頻度や送信量の制御が難しく、通信詰まりやストレージ逼迫を招きやすい 新たな統計情報を送信、取得して分析するまでのコストが高い RPC通信の問題 通信内容のデバッグが難しい、スキーマの変更と拡張に弱い 28 RE ENGINEには、ログ、テレメトリ、RPC通信それぞれに問題がありました。 ログは、バグの調査やトラブルシュートに非常に役に立ちますが、各々が好きにログを出していたため、大量に出力されるログ自体がパフォーマンスのボトルネックになったり、ログが多すぎて エラーログが埋もれ無視されたり、エラーログが出ているけど、どう対応したらよいかわからないといった問題が発生しました。 また、ログ内容の修正には、ソースコードを修正する必要があり、レビュー、マージ、配布といったコストが高かったのもログが整理されない原因でした。 そして、肝心のログはユーザーのローカル環境にしか存在せず、実際に問題が発生した時には、ログを手動で送ってもらう必要があり、既にログが消えていることも多くありました。 RE ENGINEのテレメトリ通信にも問題がありました。 エンジンの利用状況などの統計情報を取るために、テレメトリサーバーに送信していましたが、送信頻度や送信量を間違えると、あっというまに通信が詰まったり、テレメトリサーバーのスト レージを圧迫します。 そのため、新しい統計情報を送信して分析するまでのコストが高いという問題がありました。 エディタとランタイムの間で利用していたRPC通信は、通信内容のデバッグが難しかったり、スキーマを変更すると、古いランタイムと互換性がなくなって通信に失敗する問題もありました。 28
RE+X 概要 RE:Log RE:Logによるソリューション IDベースのログ管理 ログ内容とログ出力コードを分離 ソースコードの変更なしに即座にログ内容の編集が可能 柔軟な分類と機械翻訳による多言語ローカライズ バイナリ形式のログ出力 容量を最小化し、文字列化せず負荷を削減、ログ解析を容易化 全てのログを中央サーバーに集約し永続的に保存 全ユーザーのあらゆる期間のログを容易に取得 集約したログをテレメトリとして統計分析 ログとRPCプロトコルの統合 共通の通信プロトコル上にログとRPCが併存 通信内容をログ同様に永続保存し統計分析 29 これらの問題を解消するために、ログ、テレメトリ、RPC通信の統一基盤としてRE:Logを作成しました。 RE:Logでは、一般的なテキストベースのログだけでなく、IDベースのログを扱うことができます。 コードからはログIDのみを出力し、IDに対応するログの内容は外部から取得して表示します。 これによって、ログの内容をコードを変えずに後から変更することができ、ログを多言語に翻訳して表示することも可能になりました。 また、ログをバイナリ形式で出力することで、ログのサイズと出力コストを削減し、ログの解析を簡単にできるようにしました。 そして、RE:Logは全てのログをネットワーク経由で中央サーバーに集約し永続的に保存します。 これにより、ログ情報をテレメトリとしても活用することができます。 さらにRPC通信とログを同じプロトコルで統一することで、同じストリーム上でログとRPCを扱うことができるようにしました。 これによって、RPC通信の内容をログと同じように見たり、分析したりすることが可能になります。 29
RE+X 概要 RE:Log RE:Logのキーアイデア IDとCBORによるバイナリログ表現 セッション単位のログ管理 分散ログクライアントとライブストリーミング ログとRPCプロトコルの統合 中央サーバーへのログ集約と統計解析 30 それではこれらを実現するためのRE:Logの核となるキーアイデアを紹介します。 30
RE+X 概要 RE:Log
IDとCBORによるバイナリログ表現
Map
メタデータ
ログをCBORシーケンスとして表現
Array
ログデータ
CBOR:標準化されているJson互換のバイナリフォーマット
CBORのメジャータイプでデータタイプを識別
PlusInteger
フレーム番号
{"$type":"channel","name":"Test","id":1}
1
[1,-5,12222,-1,"エラーログ"]
"ラベル"
-12
MinusInteger
ドロップ数
TextString
ラベル
ByteString
圧縮CBOR列
最小コストのログ出力処理
パラメータをフォーマットなしに直接バイナリエンコード
ロック、アロケーションフリー
IDログ
ChannelId
LogId>0
Timestamp
ThreadId<0
Group
Parameters
Properties
[1, 2, 1233, -1, [true, false, 123, "text"]]
[4, 5, 34566, -2, {"property":123}]
テキストログ
ChannelId
LogLevel<=0
Timestamp
ThreadId <0
Format
Parameters
Properties
[1, -1, "デバッグログ"]
[1, -3, 1233, -5, "情報ログ{flag}", [true]]
31
RE:Logでは、全てのログデータをCBORシーケンスとして表現します。
CBORとはRFCで標準化されているJson互換のバイナリフォーマットです。
CBORのメジャータイプでデータタイプを表現し、例えばメジャータイプが配列の場合はログデータ、MinusIntegerの場合はログのドロップ数として扱います。
ログデータは、大きく分けてIDログ形式と、テキストログ形式があります。
Timestamp/ThreadId/Group/Parameters/Propertiesなどは任意で、IDログの最小構成では3バイトで一つのログを表現できます。
また、ログのパラメータは文字列にフォーマットせず直接CBORのバイナリ形式にエンコードされ、ロックやメモリ確保なしに直接ストリームに出力されます。
31
RE+X 概要 RE:Log セッション単位のログ管理 ログセッション プロセスの開始から終了までのログの集合をセッションとしてまとめたもの セッションごとにGUIDを持ち、IDからセッションを一意に特定できる ログセッションはBrotli圧縮されてサーバーに送信、保存される セッション単位の付加情報 開始、終了時間 ユーザー、デバイス、コンテンツ、アプリケーションのID セッション間の依存関係(親セッション、直前セッションのID) 32 RE:Logでは、プロセスの開始から終了までの一連のログを一つのログセッションとしてまとめて管理します。 それぞれのセッションはユニークなIDを持っているので、IDからセッションを一意に特定することができます。 つまり、IDのみを様々な手段で受け渡すことで、ログ全体を取得できます。 ログセッションは、ブロトリ形式で圧縮されてサーバーに送信され保存されます。 ログセッションには、開始、終了時間、ユーザー、デバイス、などのIDなどの情報が含まれます。 これらの情報はセッション内で変わらないので、各ログごとに持つ必要がなくなります。 親セッションや直前のセッションなどのセッション間の依存関係も持っているので、分断されたり関連する一連のセッションをまとめて取得することもできます。 32
RE+X 概要 RE:Log 分散ログクライアントとライブストリーミング ログの出力から表示、保存までの流れ LogFile RE:LogServer Profiler RE:LogAPI Archive Logs RE:LogClient Studio RE:LogClient Game Package RE:LogClient Database Live Streaming RE:LogBrowser 33 ログはRE:LogAPIを通して、ユーザー個別のログクライアントにネットワーク経由でリアルタイムに送信されます。 ログクライアントは受信したログをファイルにそのまま書き出すだけでメモリ消費や処理負荷は最小限に抑えられます。 そして、ログセッションが終了したタイミングで、ログを解析して統計情報を作成し、Brotli形式で圧縮した後、中央のログサーバーに送信されます。 出力中のログをリアルタイムで見たい場合は、RE:LogBrowserはRE:LogClientに直接接続します。 RE:LogClientはログファイルの内容をリアルタイムにストリーム送信し、RE:LogBrowserがリアルタイムにログを表示します。 アーカイブされた過去のログは、中央サーバーのデータベースから自由に検索して表示することができます。 33
RE+X 概要 RE:Log 中央サーバーへのログ集約と統計解析 RE:LogServer 全てのRE:LogClientからのログを集約し、データベースに保存 リクエストに基づいて任意のユーザー/チャンネルのログを返す ログID毎の統計解析 ログの回数、日時、ユーザー等 チャンネル毎のログカタログ情報の送受信 更新されたログ内容の機械翻訳(DeepL API) 34 RE:LogServerは、全てのRE:LogClientからのログを集約して、セッション内に、どのIDのログが何回出力されているか、などの統計情報とセッションの情報のみ をデータベースに保存し、実際のログデータは圧縮済みのデータとして通常のストレージに保存します。 これにより、データベースの容量を抑えつつ、検索や、統計的な解析を高速に行うことができます。 また、RE:LogServerは、ログIDに対応するログの表示内容を提供する役割も担います。 ログの内容が更新された際は自動的に機械翻訳が行われ、任意の言語でログを表示できます。 34
RE+X 概要 RE:Log
ログとRPCプロトコルの統合
RPCメッセージはIDログデータと同じ形式
ChannelIdがMinusIntegerの時はRPCとして扱う
奇数の時はRequest、偶数の時はResponse
RequestIdに対応するResponseを返す
RPC通信の間接モード
RE:LogClientを経由
ログと同じタイムラインで表示される
RPC通信の直接モード
アプリケーション間で直接通信しログとして表示されない
Request
ChannelId<0:奇数
RpcId>0
Timestamp
RequestId<0
Parameters
Properties
Response
ChannelId<0:偶数
RpcId>0
Timestamp
RequestId<0
Parameters
Properties
[ -3, 12, 25555, -7, [123] ]
// ID Request
[ -4, 12, 25565, -7, ["ok"] ] // ID Response
[ -3, -2, 26000, 0, "notify {0}", [42] ]
35
RE:Logでは、IDログデータと同じ形式で、チャンネルIDだけをMinusIntegerにすることで、RPCメッセージを表現します。
チャンネルIDが偶数か奇数かでメッセージが、リクエストかレスポンスかを識別できます。
RPCメッセージではThreadIdの代わりに、RequestIdを活用し、Requestに対応するResponseをマッピングします。
RPC通信は間接モードと、直接モードがあり、間接モードではRE:LogClientを経由してRPC通信を行います。
通信の内容は、ログと同じタイムラインで表示され、ログと同様に送信、保存されます。
直接モードでは、アプリケーション間で直接通信を行うため、ログとしては表示されません。
デバッグ時や、ログを残したい場合は間接モードを利用し、パフォーマンスクリティカルな大量の通信は直接モードを利用します。
35
36 次にユーザースクリプト基盤であるRE:Flowsについて紹介します。 36
RE+X 概要 RE:Flows RE ENGINEのレガシー Pythonマクロの問題 起動が遅い、型がなくデバッグとリファクタリングが難しい、動作が遅い ゲームデザイナー、アーティストには習熟コストが高い 大量のPython専用APIを用意する必要性 FSMなどノードエディタの問題 複数人での編集時にマージやコンフリクト対応が困難 パッケージ上でのデバッグが困難 APIへの参照関係がアセットを開いてみるまで分からない プロトタイピングの敷居が高い ゲームロジックはC#スクリプトのみ ゲームデザイナーやアーティストだけではアイデアの検証ができない 37 RE ENGINEには、Pythonマクロの問題、FSMなどノードエディタの問題、そしてプロトタイピングの敷居が高いという問題がありました。 まず、Pythonマクロについてです。 RE ENGINEではPythonマクロによって、ユーザーがツールを拡張できるようにしていました。 しかし、Pythonは起動が遅い、型がなくデバッグとリファクタリングが難しい、また動作自体が遅いという問題がありました。 また、スクリプト言語に慣れているプログラマやテクニカルアーティスト以外にはPythonの習熟コストが高く、一部の担当者のみがマクロを作成するにとどまりました。 他にもPython向けに大量の専用APIを用意する手間もありました。 次に、FSMなどのノードエディタの問題です。 ゲームデザイナーなどはゲームロジックの作成に、FSMなどのノードエディタを利用していましたが、複数人で編集した際にコンフリクトし、マージが難しかったり、パッケージ上でのデバッグが 難しかったり、FSMからのAPI参照がアセットを開くまで分からず、APIを仕様変更したら実行時に失敗するといった問題が発生しました。 最後にプロトタイピングの敷居が高い問題です。 RE ENGINEでゲームロジックのスクリプトを記述する際は全てC#で書く必要がありますが、C#スクリプトはPython以上に敷居が高く、ゲームデザイナーやアーティストだけでプロトタイプ のアイデアを検証することが困難でした。 37
RE+X 概要 RE:Flows RE:Flowsによるソリューション C#言語のみでビジュアルスクリプトを表現 ノード定義とノード間の接続を純粋なC#コードで定義 デバッグ、リファクタリング、検索、マージの容易化 C#ソースジェネレーターによる実行コードのコンパイル時生成 ネイティブの実行パフォーマンスとデバッガ統合 C#コードベースへの完全なアクセス 専用API不要でC#の標準ライブラリを利用可能 38 これらの問題を解消するために、ユーザースクリプト基盤としてRE:Flowsを作成しました。 RE:FlowsではC#言語のみで、ノードベースのビジュアルスクリプトを表現します。 これによって、C#エコシステムをそのまま利用でき、デバッグや、リファクタリング、API参照の検索、マージが容易になります。 ノードを実行するコードは、C#のソースジェネレーターによって、コンパイル時に自動的に生成されます。 これによって、通常のC#コードと同様に、ネイティブの実行パフォーマンスと、デバッガによるデバッグを行うことができます。 また、C#のコードベースをそのまま利用できるため、専用のAPIを用意することなく、全てのC#ライブラリを利用することができます。 38
RE+X 概要 RE:Flows RE:Flowsのキーアイデア 全てのノード情報をC#コードで定義 ソースジェネレーターによる実行コードの生成 非同期コードのビジュアルな記述と実行 C#エコシステムとのシームレスな統合 39 それではこれらを実現するためのRE:Flowsの核となるキーアイデアを紹介します。 39
RE+X 概要 RE:Flows 全てのノード情報をC#コードで定義 1ノード=1メソッド/フィールド 引数と戻り値がノードのピンになる メソッドコメントがノードのコメントになる 属性でノード座標を指定 メソッド内は任意のC#コードを記述可能 属性で接続するノードとピンを指定 40 RE:Flowsでは、全てのノード情報をC#コードのみで定義します。 一つのノードが一つのメソッドやフィールドになり、属性でノードの座標や接続関係を定義します。 メソッドの中身をユーザーが自由に記述することもできます。 左がRE:FlowsのC#コードで、右側がRE:Flowsのノード表示です。 Roslynベースで動作しているため、元のソースコードは保持したまま変更した部分のみが書き換わります。 40
RE+X 概要 RE:Flows ソースジェネレーターによる実行コードの生成 ノードとノード間の接続に基づいて実行コードが自動生成される Source Generator 41 RE:Flowsではノードとノード間の接続情報に基づいて、コンパイル時にソースジェネレーターが実行コードを自動生成します。 生成される実行コードはswitchとgoto caseを組み合わせたものになっています。 41
RE+X 概要 RE:Flows 非同期コードのビジュアルな記述と実行 非同期ロジックも直感的に作成可能 Source Generator 42 ノードベーススクリプトのメリットとして、async / awaitを利用した非同期コードを直感的に記述できます。 この例では、Text.textファイルを非同期で読み込んだ後、500ms待機しています。 42
RE+X 概要 RE:Flows C#エコシステムとのシームレスな統合 コンソール出力とデバッグ例 43 RE:Flowsで、コンソールに”Hello RE:Flows”を出力するサンプルです。 Mainから標準ライブラリを呼び出し、ブレークポイントを貼って、通常のデバッグが可能なことがわかります。 このように、RE:Flowsではビジュアルスクリプトでありながら、C#エコシステムとシームレスに連携することが可能です。 43
44 最後に次世代のランタイム基盤であるRE:Runtimeについて紹介します。 44
RE+X 概要 RE:Runtime RE ENGINEのレガシー C++コードの問題 C++言語仕様が複雑すぎ、コード品質の統一が困難 ビルド/リンク時間が⾧い、クラッシュやメモリ破壊、リークに弱い リフレクションのための独自の複雑なコーディングルール、静的に型情報を取得できない 標準化されていない内製コアライブラリ C#スクリプトの問題 C++とC#の二重の型情報 C++とC#間のマーシャルコスト、List<T>などC#コレクションとの互換性がない IL2CPP生成コードの可読性が低くデバッグが難しい メモリ管理の問題 膨大なインスタンスの生成、管理、GCの負荷が高い プレハブやシーンのインスタンス化が遅い 断片化が多くメモリのキャッシュ効率が悪い 45 RE ENGINEのランタイムには、C++コードの問題、C#スクリプトの問題、メモリ管理の問題がありました。 まずC++コードの問題として、言語仕様が複雑すぎてコードの書き方が人によって多岐にわたり品質の維持が難しいということです。 またビルドやリンク時間が⾧い、クラッシュやメモリ破壊、リークに弱いという問題があります。 そして、リフレクションのために、独自のおまじないマクロを大量に書く必要があり、実行しないと型情報を取得できないという問題もあります。 そしてファイルIOや同期オブジェクトなどは、独自にラップした内製のコアライブラリを利用する必要がありました。 次に、RE ENGINEでは全てのゲームコードをC#スクリプトで記述しますが、これにも問題がありました。 一つはリフレクションのための型情報がC#とC++で二重になり実行ファイルのサイズが大きくなることです。 そして、ListのようなジェネリックコレクションをC++と直接やり取りすることはできませんでした。 またパッケージ作成時にはIL2CPPによって、ILをC++コードに変換するのですが、変換したC++コードは可読性が低くデバッグが難しいという問題がありました。 最後にメモリ管理の問題です。 現在のゲームタイトルではメモリ上に数千万インスタンスが存在するようになり、物量の増加に伴ってインスタンス生成の速度やGCの負荷、キャッシュ効率など様々な問題がでてきました。 45
RE+X 概要 RE:Runtime RE:Runtimeによるソリューション RE:C++言語の導入 C#コードを一対一で対応するC++コードにトランスパイル C#ジェネリクスをC++テンプレートとして表現 C#標準ライブラリを利用可能 独自のGCアルゴリズムをコードレベルで統合 ページ単位のメモリ管理 全てのインスタンスを64KBのページ単位で管理 ページ単位のインスタンス化、ページ参照カウンタ、 キャッシュ/GC最適化 46 これらの問題を解決するために、RE:Runtimeでは独自のRE:C++言語を導入しました。 RE:C++言語は、C#コードと一対一で対応するC++コードを記述可能な独自の言語仕様です。 基本的にC++と互換性があり通常のC++コンパイラでそのままコンパイルすることができますが、 C#標準ライブラリを利用できる、C#のジェネリックコレクションをC++で利用できる、独自のGCアルゴリズムを統合している などの違いがあります。 また、RE:Runtimeはページ単位のメモリ管理を導入します。 インスタンスごとにメモリを確保するのではなく、全てのインスタンスを64KBのページ単位でまとめて管理します。 そして、インスタンスの作成、解放やGCはページを基準に行います。 46
RE+X 概要 RE:Runtime RE:Runtimeのキーアイデア C#と等価な表現を実現するRE:C++言語仕様 C#をランタイムのフロントエンド言語へ C#エコシステムの活用 C++バックエンドとのシームレスな統合 ページ単位のインスタンス管理 マイクロアレイによるキャッシュ効率の最大化 47 それではこれらを実現するためのRE:Runtimeの核となるキーアイデアを紹介します。 47
RE+X 概要 RE:Runtime C#と等価な表現を実現するRE:C++言語仕様 C++23をベースに、C#と等価なコーディングを可能にする独自言語仕様を作成 デストラクタ無効、自動メモリ管理 ジェネリクス、インターフェイス、ボクシング、例外処理をC++言語で表現 .NET標準ライブラリを利用可能 C# RE:C++ 48 RE:Runtimeは、RE:C++言語で記述されます。 RE:C++は、C++23をベースにC#と等価なコーディングを可能にする独自の言語仕様です。 通常のC++とは異なり、デストラクタが無効化され、自動メモリ管理が前提になります。 そしてC#のジェネリック、インターフェイス、ボクシング、例外処理等をC++言語で表現します。 そして、.NET標準ライブラリをそのまま利用することができます。 48
RE+X 概要 RE:Runtime C#をランタイムのフロントエンド言語へ ランタイムは主にC#言語で構築 メモリ管理、属性、リフレクション、C#への公開インターフェイスなどの問題を スマートに解決 C#からRE:C++に変換してビルド RE:BuildがdllをC#コードにデコンパイルしてRE:C++コードに自動変換 vcxprojやslnは、csprojと依存関係から自動生成 C#ランタイムコード csproj C#dll csc RE:C++ デコンパイル vcxproj/sln Lib/Exe msbuild RE:Build 49 RE:Runtimeではランタイムは主にC#言語で記述します。 これによって、C++で問題になっていた、メモリ管理やリフレクションのための独自のおまじないマクロ、タイトルスクリプトへのC#のAPI公開などの問題をスマートに 解決できます。 ビルドの流れですが、まずC#で書かれたランタイムコードは一旦C#のdllにコンパイルされます。 そして、RE:BuildツールがdllをC#コードにデコンパイルして一対一で対応するRE:C++コードに変換します。 最後にC++コンパイラがRE:C++コードをビルドして実行ファイルを作成します。 49
RE+X 概要 RE:Runtime C#エコシステムの活用 C#標準ライブラリの利用 安定し高度にドキュメント化された豊富なライブラリ C#標準のユニットテストの利用 ランタイムを起動することなく即座に単体テストを実行 50 ランタイムのほとんどをC#言語で記述することで、C#のエコシステムをフルに活用することができます。 高機能なエディタとリファクタリング、高速なビルドとユニットテスト、安定した標準ライブラリなどが全て利用できます。 50
RE+X 概要 RE:Runtime C++バックエンドとのシームレスな統合 C#でextern宣言したメソッドのみC++23で直接記述 外部ライブラリの利用や、パフォーマンスの重要な部分のみ C++インスタンスはC#のnint型フィールドにポインタを入れ、Finalizerで明示的に解放 C#側でメソッドをextern宣言 C++23でメソッドを手動記述 51 外部ライブラリの利用やパフォーマンスクリティカルな部分は、C++言語で直接記述することができます。 C#側でメソッドをexternで宣言し、C++言語でメソッドの中身を自由に記述できます。 ただしネイティブのインスタンスを扱う場合は、C#のFinalizerを利用するなど注意が必要です。 51
RE+X 概要 RE:Runtime ページ単位のインスタンス管理 全てのインスタンスを64KBのページ単位で扱う 仮想メモリのページと一対一で対応 インスタンスアドレスの下位16bitをマスクすると、ページになる 64KB Page H InstanceA 64KB Page InstanceB H InstanceC InstanceD InstanceE ページ単位のインスタンス化 シリアライズデータの事前作成 • 64KB単位でインスタンスのメモリマップを保存 • キャッシュ効率を考慮してメモリマップを事前最適化 高速なデシリアライズ • メモリマップをコピーした後、アドレスをパッチ 52 RE:Runtimeではページ単位のインスタンス管理を採用します。 全てのインスタンスはページに所属し、ページは共通の管理領域を持ちます。 そしてシーンやプレハブのインスタンス化をページ単位に行います。 事前にインスタンスのメモリマップをそのまま保存しておき、その際キャッシュ効率を考慮してメモリの配置を最適化します。 インスタンスを実体化する際は、ページにメモリマップをコピーした後、アドレスのみをパッチします。 このようにすることで、大量のインスタンスを非常に高速に作成できるようになります。 これが実現可能になったのはRE:C++言語によって事前にインスタンスのメモリマップを確定できるようになったからです。 52
RE+X 概要 RE:Runtime ページ参照カウンタとGC最適化 インスタンス単位に加えて、ページ単位で参照カウンタを持つ シーンやプレハブは、ページ単位で参照カウンタを上げる ページ内のインスタンスが全て解放されても、 ページが参照されている場合はメモリは解放されない GC最適化 Local/GlobalFrameGCはページ単位で最適化 CycleGC時は参照されているページのインスタンスは追跡から除外 53 またページは参照カウンタを持ち、ページ内のインスタンスが解放されてもページが参照されている場合は解放を行いません。 シーンやプレハブから作成したページは、シーンやプレハブの所有権を持ち、シーンやプレハブが解放されたときにページも解放します。 これによって、ページが参照されている場合は、ガベージコレクションの対象外にすることができ、大規模なシーンにおいては大幅な高速化を行うことができます。 53
RE+X 概要 RE:Runtime マイクロアレイによるキャッシュ効率の最大化 インスタンスがメモリ的に連続していることを保証するメタ配列 参照型配列とインスタンス配列、可変⾧配列のハイブリッド メモリ、キャッシュ効率とインスタンス単位の管理の柔軟性を両立 マイクロアレイ同士の結合、アトミックなインスタンス列挙が可能 64KB Page MicroArray H Joint1 H Joint2 H Joint3 H Joint4 H Joint5 H Joint6 H Joint7 配列解放 H Joint3 H Joint6 外部参照 54 また、マイクロアレイという新しい配列の概念を導入します。 これは、インスタンスがメモリ的に連続していることを保証するメタ配列です。 C#言語には、参照型配列と値型配列がありますが、参照型配列には、ポインタと実体が離れていてキャッシュとメモリ効率が悪いという問題があり、値型配列に は配列の個別要素への参照を直接保持できないという問題がありました。 マイクロアレイでは、参照型のインスタンスがメモリ的に連続していることを保証し、インデックスでの高速な列挙が可能です。 また、配列内の要素への参照を個別に持つことができ、マイクロアレイが解放されても、参照されているインスタンスのみが残ります。 RE:Runtimeではコンポーネントをマイクロアレイとして持つことで、メモリ的に連続したコンポーネントを効率的に列挙することができエンティティコンポーネントシステ ムのメモリ効率と柔軟性のバランスを両立させます。 54
55 最後に、REXプロジェクトが目指すビジョンを紹介します。 55
REXビジョン AI世代のゲームエンジンへ C#エコシステムへの統合 ランタイムからUI、ビジュアルスクリプトまでを純粋なC#言語で構築 AIがコードの理解・テストを一貫して行える型安全な開発環境を提供 AIエージェント・ファーストな設計 ログとRPCを統合し、 AIがシステムを動的に操作・観測・検証できる構造を実現 仮想化UIによりCLIベースの検証サイクルを高速化 オープンソースによるエコシステムの進化 基盤技術の「RE:Dox」をGitHubで公開、 AIが参照可能な公開知識として蓄積 知識の集約とAIによる活用ループを回し、継続的に生産性を向上 56 これまで紹介してきたREプラスXの新しい技術をRE ENGINEに導入していくことで、RE ENGINEはAI世代のゲームエンジンへと進化します。 まず一つは、C#エコシステムへの統合です。 REXでは、ランタイムからUI、ビジュアルスクリプトまでを純粋なC#言語で作成することができます。 C#言語は既に膨大な学習ベースがあり、型安全な言語なので、AIがコードの理解、テストを一貫して行えるようになります。 次にAIエージェント・ファーストな設計です。 ログとRPCの統合や仮想化UIにより、AIエージェントがエンジンの操作と観測、検証をコマンドラインベースで高速に行うことができるようになります。 そして、オープンソースによるエコシステムの進化です。 RE:DoxをGitHubでオープンソースとして公開することで、AIがコードを参照し、学習に利用できるようにし、精度を向上させます。 このように、REXではAI時代に適応したゲームエンジンを目指していきます。 56
REXビジョン CAPCOM GitHub https://github.com/CAPCOM-TD-OSS/ 57 RE:DoxはCAPCOMのGitHubページから利用可能ですので、ご興味のある方はぜひご覧になってみてください。 57