見出し画像

[Pyxel] 自作ライブラリ:Window系

自作ライブラリの中身の話を少しずつしていこうかなと思います。
処女作からメニュー関係のクラスは継ぎ足し継ぎ足し秘伝のタレのように(前にも使ったフレーズ)使い回してきて、課題というかやりたい事は割と明確でした。

  • メニュー⇔サブメニューの階層制御/管理の単純化

  • 選択項目と選択時動作を予め関連付けておく構造

  • 窓サイズをフォントに合わせて動的設定

  • メニュートップではない階層からの画面遷移やメニュー全クローズ処理の簡素化

この辺りを狙って作った仕組みです。

パッケージ構成

関連クラスを纏めてローカルパッケージを作ってます。

ファイル一覧

WINDOW
├ chip_window.bmp
├ window_base.py
├ window_manager.py
├ window_protocol.py
├ _wrapper_input.py
└ __init__.py

ファイル概要

【chip_window.bmp】

8x8pxの、Pyxel Editorでも管理しやすい画像

windowクラスで使う為の、Window画像用イメージチップ。
上下左右の辺と四方隅の枠と、カーソルアイコンを含む。
枠内の塗りつぶしは、以前は塗りつぶし用チップを敷き詰めていたのですが、どう考えてもpyxel.rectでガバっと塗る方が速いよなと思ってやめました。なお塗りつぶし色は上記画像の(7,7)の色を取ってくる形になってます。

本パッケージを含む共通ライブラリ集、みたいなものを作ってまして、最上位に配置したコンフィグファイルでファイル名やパスを変更可能です。


【window_base.py】

中核モジュール。
Windowクラス:指定サイズの窓を保持して描く機能+そこに指定されたテキストを載せる機能。
MenuItemクラス:Menuとしてウインドウ表示する時の表示内容と、選択時に実行される処理を纏めたクラス。
ExecResultおよび継承クラス:操作結果を表すクラス。操作に応じたウインドウ操作(サブメニューを閉じる、選択メニューを実行するなど)を行う為の戻り値です。
もともとは後述するWindowAcitionをメニュー選択時の戻り値としていたのですが、サブメニューを展開する時には引数等単一で収まらない情報が欲しいなと思って拡張。
(現状ではサブメニュー展開以外はpassするだけですが。。。)
Menuクラス:メニュー項目・カーソルの描画と制御、選択時の後続処理実行等を管理。
下地となるウインドウは自身が内包するWindowクラスを利用します。
固定メニュー項目と動的メニュー項目(例えば所持品一覧は時々の状況で変わるので動的だけど、フィールドメニューは毎回同じ内容が開く)で処理を分けていて、固定メニュー項目は指示子指定、動的はメニュー構成辞書指定、でその内容を元に処理を行っていますが、全部統一した方が分かりやすいかも知れません。
型危険な書き方になっているのでちょくちょくバグります。
WindowInputHandlerクラス:入力キーに対する名前付けを行うクラス。
将来記法が変わった時の為にプログラム内では独自のキー入力名(decide,cancel,up,down等)でコードを書いて、そこに実際のキー入力(pyxel.btn等)を紐づけてます。
inputライブラリも自作しているのですが、それを使っても使わなくてもいいようになってます。
inputライブラリについてはまた後日。
MenuYesNoクラス:未実装。もともと作っていたMenuクラスで定義していたものですが、はい/いいえのメニューはどこでも使いそうなのでいずれ実装する予定です。


【window_manager.py】

メニューやウィンドウをスタック管理する人。えらい。
外部からの依頼に応じてMenu/Windowクラスのスタック追加/削除を行い、drawフレームでは一番奥から順に全て描画、updateフレームでは一番手前のスタックのみ処理して、戻り値によって処理を振り分けています。

こいつで一番やりたかったのは「メニュー階層の全閉じ」
初代のメニュークラスは、クラス内でキー判定→自身のサブメニュー変数に次のメニューを登録→次のメニュー内でサブを呼んだら更にサブへ、と再帰的な構造になってました。そのせいで「この機能を実行したら一発で全部閉じる」みたいなのをグルグル遡ってやらないといけなかったのと想定外が頻発して九龍城みたいな継ぎ足し具合になっていたのを問題視していて。
じゃあ管理者を一人立てて、毎回「次の階層にこの子を追加お願い!」とみんながお願いするようにしたら、管理者に依頼すれば一発で全閉じも出来るし、現在のウインドウから閉じるコールが掛かったらスタックの端をpopするだけで自動的に一つ前(上?)の階層に処理が遷移して便利、という目論見でした。
スタックに追加、スタックから削除、スタックを消す、スタックの数を返す、ぐらいの機能しかないのですが、中核モジュールを制御している一層えらいクラスです。


【window_protocol.py】

インタフェースモジュール。
共用する決め事的な値を定義しています。
例えばWindowActonクラスでは、Window操作(決定、キャンセル等)時の戻り値を定義しています。
タイポを防いだり、マジックナンバーを発生させない事が目的です。
ライブラリ外から操作する為にこのクラスをimportしたい状況で、window_manager.pyに書いてて循環参照が発生したので切り分けました。
ちなみに他のパッケージでもこういう決め事関連は全部外出しで書くようになりました。


【_wrapper_input.py】

WindowInputHandlerクラスの土台。
Windowモジュールでの「決定キー押下」がプログラム的に何を行うかを表すクラス。デフォルトで自分が良く使うキーマップを入れてますが、キー入力名(upとかcancelとか)はCallableで定義してあるので、処理を呼び出す物でなんぼでも上書きできます。


【__init__.py】

あんまり説明も要らないとは思いますが。
上記のパッケージ構成ファイルのインポート用情報が書かれています。
ライブラリを使う他のファイルで「from window import クラス名」のようにモジュール名をいちいち書かずに導入する、というのがファイルの概ねの目的です。

ゲームを作りながら不足や考慮漏れの機能補完、バグ修正などを行っていますが、完成したらGitHubにでもアップするかも知れません。