メインコンテンツまでスキップ
バージョン: 1.3.0

ビューワープログラム

1. 概要

image

ビューワープログラムは json 出力されたコリジョンチェックの結果ファイルを読み込み、 その結果を GUI 上、並びにゲーム上に可視化するためのツールを構成するものです。 ゲームバイナリとビューワープログラム間の通信は Game-Python Bridge を介して行っています。 GUI 作成には DearPyGUI を利用し、ビューワープログラムは MVC モデルで作成しています。

関連ファイル

ビューワープログラムに関連するファイルは以下です。

Python
├── alfort +---------------------+ Alfort 関連ディレクトリ
│   ├── custom_env_worker.py +---+ サブスレッド上で動作する Unreal Engine 通信モジュール
│   ├── custom_util.py +---------+ ゲーム固有の汎用関数
│   └── debug_launcher.py +------+ タイトル画面 / デバッグ機能等の操作モジュール群
├── res +------------------------+ 素材データディレクトリ
├── result_viewer.py +-----------+ ビューワーツールプログラム
├── utils +----------------------+ 便利モジュールディレクトリ
│   ├── logger.py +--------------+ ログ出力モジュール群
│   └── util_modules.py +--------+ 汎用関数群
└── wrappers +-------------------+ ゲーム通信ディレクトリ
└── ue4_wrapper.py +---------+ Unreal Engine 通信モジュール

2. プログラムの詳細

image

MVC モデルを参考にビューワープログラムを作成しており、 ビューワープログラムでは Main_Controller クラス、Main_View クラス、Main_Model クラスの 3 クラス間で情報をやり取りすることでシステムを提供しています。 プログラム解析や拡張を行う場合、まずは下記の引用元を参考にして MVC モデルの概要を把握することを推奨します。

備考

MVC モデル
役割ごとに Model, View, Controller に分割してコーディングを行うモデル
それぞれの役割は,

  • Model --> システムの中でビジネスロジックを担当する
  • View --> 表示や入出力といった処理をする
  • Controller --> ユーザーの入力に基づき,Model と View を制御する

引用元: https://qiita.com/s_emoto/items/975cc38a3e0de462966a

エントリーポイントでは Main_Controller クラスのインスタンスを生成し、 クラス内部で他 2 つのクラスインスタンスを生成後に各種セットアップと GUI 描画処理を実行しています。

# result_viewer.py
# エントリーポイント (簡略コード)
if __name__ == '__main__':
# コントローラーを生成
controller = Main_Controller()
# セットアップ
controller.setup()
# GUI表示
controller.show()

セットアップ関数内ではモデル Main_Model とコントローラー Main_Controller を生成し、セットアップを実行します。

# result_viewer.py
# エントリーポイント (簡略コード)
class Main_Controller:
def setup(self):
# DearPyGUI 初期化
dpg.create_context()
# モデル生成
self.model = Main_Model()
# ビュー生成
self.view = Main_View(self)
# DearPyGUI 内部の値レジストリにツールで参照書き込みする値を事前登録
self.model.registry_value()
# ツール専用のセーブデータがあれば事前読み込み
self.model.load_data()
# DearPyGUI を利用して UI を構築
self.view.setup()
# 読み込んだセーブデータの値に応じて諸々処理を実行
self.view.check_loaded_data()
# コントローラー自身のセットアップ処理
self._setup_myself()

GUI 操作全般を Main_View クラスに移譲しています。 そのため GUI 関連のセットアップについても self.view.setup() 関数内部で実行されます。 本プログラムでは GUI 描画にDearPyGUIを利用しており、 2023 年 3 月末時点ではバージョン 1.7.1 を前提としています。

# result_viewer.py
# DearPyGUI を利用して UI 構築 (簡略コード)
class Main_View:
def setup(self):
# デスクトップ解像度を取得
monitor_info = GetMonitorInfo(MonitorFromPoint((0, 0)))
work_area = monitor_info.get("Work")
self.screen_width = work_area[2]
self.screen_height = work_area[3]

# DearPyGUI に日本語表示可能なフォントを登録
self.register_font(self.__font_size)
# GUI ウィンドウを作成. 画面の右下に生成されるように設定
dpg.create_viewport(title='Morikatron CollisionTest Tool', width=self.viewport_width, height=self.viewport_height,
x_pos=self.screen_width - (30 + self.viewport_width), y_pos=self.screen_height - (30 + self.viewport_height))

# メニューバーを設定
with dpg.viewport_menu_bar():
with dpg.menu(label="バージョン情報"):
self.make_menu_version()

# GUI ウィンドウ内に内部ウィンドウを生成し、「ゲーム設定」タブと「結果確認」タブの表示設定
with dpg.window(label="リザルトビューワー", tag="Result_View", no_move=True, no_close=True, pos=[0, 25], no_scrollbar=False, width=330, height=485):
with dpg.tab_bar(label="tb"):
with dpg.tab(label="ゲーム設定"):
self.make_game_tab()
with dpg.tab(label="結果確認", tag="result__tab"):
self.make_result_tab()

ボタン押下、スライド操作、ファイル選択等の全ユーザー操作に対してコールバックを設定し、 全てのユーザー操作から controller.callback_event(key, args) 関数が呼び出される仕組みになっています。

以下に「ゲーム設定」タブの表示プログラムを例として示します。

# result_viewer.py
# DearPyGUI を利用して UI 構築 (簡略コード)
class Main_View:
def make_game_tab(self):
dpg.add_text("ゲームの実行ファイル")
with dpg.group(horizontal=True):
dpg.add_input_text(tag="game__text_exefile", source="game__val_exefile", readonly=True, width=170)
dpg.add_button(label="exeファイルを選択", tag="game__select_exefile", callback=lambda: self.__ctrl.callback_event("game__select_exefile", ["game__val_exefile"]))
dpg.add_text("テスト結果フォルダ")
with dpg.group(horizontal=True):
dpg.add_input_text(tag="game__text_resultfolder", source="game__val_resultfolder", readonly=True, width=170)
dpg.add_button(label="フォルダを選択", tag="game__select_resultfolder", callback=lambda: self.__ctrl.callback_event("game__select_resultfolder", ["game__val_resultfolder"]))
dpg.add_text("マップ選択")
with dpg.group(horizontal=True):
dpg.add_listbox(tag="game__select_map", source="game__val_mapname", items=self.__ctrl.model.get_map_list(), num_items=len(self.__ctrl.model.get_map_list()))
self.__make_line(height=10)
dpg.add_checkbox(label="コリジョン抜け検出のためのデバッグ表示", tag="game__flag_draw_raycast", default_value=True)
with dpg.group(horizontal=True):
dpg.add_button(label="起動", tag="game__run", enabled=False, width=315, height=50, callback=lambda: self.__ctrl.callback_event("game__run"))
# result_viewer.py
# イベントコールバック関数 (簡略コード)
class Main_Controller:
def callback_event(self, key, args=None):
# ゲーム設定タブ関連キー
if "game__" in key:
if key == "game__select_exefile":
# 選択ボタンを押されたら、View モジュールでファイル選択ウィンドウを開き、選択後に Model モジュールで設定を保存
self.view.select_exefile(lambda path: self.model.save_settings())
if key == "game__select_resultfolder":
# 選択ボタンが押されたら、View モジュールでフォルダ選択ウィンドウを開き、選択後に Model モジュールで選択パス文字列の保存
self.view.select_resultfolder(lambda path: self.model.set_dpg_path(args[0], path))
if key == "game__run":
# Model モジュールで結果ファイルとコリジョンファイルを読み込み
self.model.read_resultfile()
self.model.read_collisionfile()
# View モジュールで結果確認タブを自動的に開く
self.view.open_result_tab()
# Model モジュールで対象マップ用セーブファイルをゲーム読み込み用フォルダへコピー
self.model.copy_savefile(folderpath, map_keyword)
# Model モジュールでゲームを起動
self.model.run_exe(is_draw_raycast=is_draw_raycast)
# View モジュールでマップウィンドウを開く
self.view.make_map_window()
if key == "game__quit":
# Modelモジュールでゲームを終了
self.model.quit_exe()
# 結果確認タブ関連キー
if "result__" in key:
pass # 省略
# マップ表示関連キー
if "map__" in key:
pass # 省略
# 落下点一覧ウィンドウ関連キー
if "dropwindow__" in key:
pass # 省略
# 怪しい箇所一覧ウィンドウ関連キー
if "lackcolwindow__" in key:
pass # 省略