赤宿 = Red Inn

素人の試行錯誤と2Dローグライク制作 (GUI)

Top

『桟橋』が2Dゲームを作るブログです。右のカテゴリ一覧からどうぞ。

 素人のブログなので気をつけてください。

過去に作ったゲーム

 ウディタローグライク:

  • 羊山ゴートの冒険 [2013/8]
     一作目。ウディタではゲームの流れを直接記述できたのが良かった。

  • ツギハギの行方 [2015/8]
     二作目。UIは良くなったが、ゲームと呼べない完成度。

現在

Vimの所感

 キー操作と操作履歴が真髄だと感じた。プラグインはサポート程度。

 マウス無しでテキストを編集するなら、Vim以外でやる気にならない。

Vimの色々

Vimの種類

 本家Vim、Neovim、GUI実装のVimなどがある。EmacsVim風のキーバインディングを取り込んだ拡張(Spacemacs)もある。

 本家Vimの動作が重いため、僕はNeovimを使っている。

Vimのキー操作

 絶対に必要というほどではないが、日常的に使いたいくらい強力だった。

モードが分かれている modal

 挿入モードiが分かれているため、編集に豊富なキーを利用できる。シフトキーの代わりはvsisualモード。

modelessなCLIエディタEmacsは、対照的に修飾キーの使用が多いらしい。

操作+範囲 operator + motion

 組み合わせで操作ができる。

 e.g. d$  行末まで消去
 e.g. cf。 文末まで置換

 カーソル移動が単語ベースになるなど、基本的な操作も改善される。頭文字や正規表現との対応のおかげで、無理無く暗記できた。(uでundo、素晴らしい!)

履歴の単位が優れている

 一連の操作が一回分の操作履歴となるため、リピートやredo/undoが非常に強力。削除dではなく置換cを使うなど、小技も効く。

Q. でも、(学習コストが)お高いんでしょう?

 マッスルメモリを鍛えてください(誤用)。

 学習曲線は二次関数的なイメージです。

Vimの活用

Vimを使う場面

 主にrcファイルの編集。csvmarkdownGUIエディタで編集したい。

操作感を他の環境に導入できる

 思わぬボーナス。

IDE

 プラグインVimになった。

ブラウザ

 アドオンVimimを入れた。ショートカットの拡張程度のもの。左手でスクロールしたり、キーボードからリンクを開ける。

Vimプラグイン

 最も簡単と聞いて、vim-plugで入れている。プラグイン毎に、やや設定に時間が必要。

f:id:samba_4e:20190118200526p:plain
表示が崩れており、まだプラグインは設定中

 プラグインには、常駐型エクスプローラ、ZENモード、リッチなステータスラインなどがある。

Vimの本

 自分用のチートシートを用意して、一通り操作を覚えた。本も読んでみる。

実践Vim 思考のスピードで編集しよう!

 Vimの操作が改善される本。序章でリピートキー.;n@を導入し、次章からvimゴルフが始まる

Vimテクニックバイブル ~作業効率をカイゼンする150の技

 主にVimの設定の変更に役立つ本。ネットで調べるよりも遥かに速そう。

r/rldevのチュートリアルをやる ~ Part1

内容

 Roguelike Tutorial Revisedのlibtcod編をやっていく。今回はPart 1のみ。最近慣れてきたCLI上で、シェルやGitを使っていく。そちらがメインで、簡単な解説付き。

注意点

 チュートリアルの参照先の環境構築法が古い

 使用されているlibtcodpyは導入が難しく、メモリリークの危険性もある。代わりに、python-tcodCLIで手に入れて使う。libtcodpyと互換性があるAPIのため、共通のチュートリアルに沿うことができる。

 そもそも、Rogue BasinにもPython3 + libtcod (python-tcod) のチュートリアルがある(Python2ではなく)。こちらでは、vshの仮想環境にライブラリを入れている。

 また、libtcodには、Sprite等が無い。もしもGUIのゲームを作るなら、PyGameなどにUIを任せ、libtcodはModelのEngineとして使う。または、全く別の環境で作る。

 今回はAscii風画面のゲームを作って満足する予定なので、python-tcodで問題無かった。

環境構築 (Mac向け)

 パッケージマネジャ経由で入れるだけで済む。仮想環境ではなくグローバル環境に入れた。

Python3 + pip3

 ターミナルを開く。python --versionを入力しEnterを押すと、初期のMacでは2系を示すはず。

$ # Homebrewが無ければインストール
$ brew --version || ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ # Python3が無ければインストール(pip3も付いてくる)
$ python3 --version || brew install python3

 ||演算子については、man shman bashに載っている。manで起動されるページャ(less)の操作法は、hキーから見られる。

python-tcod

 pipでインストールすれば、パスが通り、すぐに使えるようになる。(import tcodとできる)

$ pip3 install tcod

 ライブラリ用のフォントをこちらからダウンロードしておく。今回は、チュートリアルで使用されているフォントを使う。(フォントの規格に合わせた設定が必要な模様)

VS Code(エディタ)

 .pyファイルを編集している内に、補完(予測)が利くプラグインをエディタに勧められ、気づけばリッチな環境になっていた。

Vimでコーディングする機会は無いかもしれない。

Git

 初期でMacに入っているものは最新版ではない。この際、Homebrewで新しく入れた。

$ git --version
<古いバージョン>
$ brew install git
$ git --version
<新しいバージョン>

 チートシートを刷る。出力を着色するように設定しておく。

$ git config --global color.ui auto
$ git config --list
credential.helper=osxkeychain
...
user.name=...
user.email=...
color.ui=auto

Coding

 Part1 Drawing the '@' symbol and moving it aroundをやっていく。

Git

 git initで現ディレクトリを初期化。

サブコマンドに補完を利かせる

 この記事が参考になる。(ただし、.bash_profile内でsource ~/.bashrcとしておく必要があると思う)

$ sudo find / -name git-completion.bash

 fishシェルでは、特に設定は要らなかった。

.gitignore

 監視から除外するファイルを設定するファイル。gitによって自動的に利用されるが、特に.gitignoreを編集するコマンドは無い。自分で書いた。

$ cat << EOS > .gitignore
> .DS_Store
> __pycache__/
> EOS
$ ls -AF
.git/    .gitignore

 here documents <<を使って書いてみた。where式に近いと思う。

module管理

 Python3には名前空間があるとか。今回は__init__.pyを設置した。

ファイル管理

 git add現在のファイルの状態をコミット予約する。ファイルの移動・削除はgit mvgit rm経由で行う。(継続した履歴を付けるため)。

 実はgit guiGUIツールを起動できるみたい。ファイルの移動などは、GUIの方が楽そう。でも先にCLIを身につけてみる。

git commit

 Part 1を終えた段階で記録を付ける。@を動かせるようになった。

f:id:samba_4e:20181226170515p:plain

 今後も習得をCLIに絞る方向性で行く。

Mac環境のNotes

 ショートカットは、環境やアプリ毎に印刷するのがオススメ。

アプリ (Mac専用)

iTerm2

 pane分割(画面分割)などが優れたターミナル。

CotEditor

 縦書きに最適なエディタ。段組みはできない。フォルダを開く機能は無い(タブしか無い)。

 空白や改行を表示する。改行間隔を調整する。縦書き・横書き変更のボタンを分かりやすいところに置く。

chunkwm

 タイル型ウィンドウマネジャ。ウィンドウの自動的な配列やリサイズをし、実質的に画面を大きくしてくれる。ショートカットでフォーカスを移動できる(キー操作は他アプリで関連づける)。

Manico

 アプリのランチャ。(アイコンを見ながら)ショートカットで選択できる。

MacDown

 左画面にマークダウン、右画面でプレビュー。

MacVim

 MacgVim。ターミナル上で動く本家よりも、速くて高機能。

アプリ (マルチプラットフォーム)

Atom

 テキストエディタとしても非常に優秀。何でもある。(複数の)フォルダを開く機能もある。Atom MaterialというUIテーマが良い。僕はsyntaxテーマにsmyckを使用中。

mdbook

 最も簡単な静的サイトジェネレータ (SSG)。Rust本を具体例にできる。

その他アプリの設定

ブラウザ (Firefox)

 ツリー型タブを導入し、上部のタブを消すGoogleなどのテーマを差し替え、黒くする(また、一部サイトを縦書きに)。

 新規タブ画面をLauncPad風にする拡張も素晴らしい。ただ、アドオンは全部スパイウェアだと思っておいたほうが良さそう。

Macの設定

環境設定

 キーリピートマウスポインタ、スクロールの速度を最速に。Launchpadにショートカットを付ける。Ctrl+nでn番目のデスクトップに移動。

ターミナルから(要検索)

 起動時の音(ジャーン)を消す。スリープからの復帰を最速に。Finder上部に絶対パスを表示。FinderからiTerm2を開くショートカットを設定。隠しファイルの表示をデフォルトに。

ショートカット

 チートシートを刷って一望するのが良いと思う。一部だけメモ。

アプリ一般

 Cmd+, で環境設定を開く。Cmd+n, Cmd+{}でタブ移動。Cmd+[]でpane移動。

テキスト入力
記号 読み 入力(ショートカット )
三点リーダ Opt+.
emダッシュ(全角ダッシュ) Opt+Shift+-
カーソル移動(EMacs風キーバインディング
入力 読み 効果 入力 読み 効果
^a 行頭へ移動 ^e end 行末へ移動
^f forward 前へ移動 ^b backward 後ろへ移動
^d delete 前を削除 ^h 後ろを削除
^n next line ^p previous line
記号 効果
^k 行末まで削除
^l カーソルが画面中央になるようスクロール
Finder
入力 機能
space プレビュー
Enter リネーム
Cmd+Delete 削除
Cmd+o 開く
Cmd+Opt+V ペースト & コピー元を削除(カットは無い)
Cmd+Opt+C 絶対パスをコピー
Cmd+Shift+G パスを入力して移動
Cmd+Shift+D,A,H デスクトップやアプリ、ホームへ移動
Cmd+i 情報を表示

 拡張子毎にデフォルトの起動アプリを変えるには: 情報を表示 -> このアプリケーションで開く -> 全てを変更。

シェル関連の読書

 タイトルはAmazonへのリンクとなっている。どの本も、特に序盤が面白い。色々な技術書の最初の方だけを読んでおくと、導入は楽になるかもしれない。

シェルプログラミング実用テクニック(2015/06出版)

 ワンライナ(one-liner program: 一行のプログラム)を書くための本。パイプでコマンドの入出力を繋ぎ、単機能のコマンド(フィルタ)を複数回適用することで、データを加工するのがシェルの基本だと明言し、以後、終わりまでそれを実践する。

 僕が最初に(二章まで)読んだ本。シェルとは、まず、テキストやディレクトリに対し、一回性の操作を行うためのものであると学んだ。(必要なら、その操作を保存して再利用できる)。

 文章の冗長性が(良くも悪くも)高く、逆引き辞典として使うには内容が重い。代わりに、一度理解すれば知恵が身に着き、以降読み直す必要が無くなる。数回読み返すことで、ワンライナを書いていく工程と、文字列加工のための方針、コマンドの詳しい使用法が、実用的な文脈で分かった。

 この本からシェルに入門したため、基本を抑えるために、他の場所を探し回ることになった。UNIXの考えも載っているが、元のアイデアを参照する程度なので物足りない。

 30%読んだ。興味ある項目が残されているのが嬉しい。経験上、初見で読み解くのが難しいため、他の場所で下調べをしてから臨もうと思う。その後のレベルアップに付き合ってくれる本だと思う。

ネット記事(Stack OverflowやQiita、個人ブログ)

 要所のスクリーンショットを保存した。(Macさんは、そういうことへの配慮が上手い)。スクラップは参照性が高く、肝心なときに引っ張り出すことができる。

 先にチートシートを刷っておけば良かったと思う。

 こちらの記事が未消化。内容もさながら、スラスラとここまで詳しく語れることに注目したい。

試行錯誤

 自身の取り組みも読書の一部で、むしろ中心。やりたいことが増えて来たので、数年は飽和しないと思う。

新しいLinuxの教科書(2015/06出版)

 素晴らしく気が利いた本。暗黙的な100の知識を身につけるためのもの。読者は「簡単」とか「当たり前」とされることを脳内にインストールし、また、各分野に入門するための細部を押さえることができる。

 四章までは必須の知識が書いてある。五章からは、明治的ではないが、基礎知識の辞典になっているように思える。情報量を抑えつつも、学習のための知識は細かく書いてある。(Ctrl+rでコマンド履歴を検索できることは知っていましたか?)。

 この本を読む前に散々ネットを徘徊していたので、単品として読んだわけではない。目的に沿った順番で読んでいる。

 70%読んだ。各章末のカラムも良くて、それは読み物として大切なことだと思う。

Classic Shell Scripting(2005/05出版 和訳は2006/01)

 UNIXの文脈の下で、シェルについて読むことができる。知識も密になっていく。30%読んだ。

The UNIX Philosophy(1994/02出版 和訳は2001/02)

 良文を脳みそに打ち込んでくれる、150ページほどの冊子。20%読んだ。合わせて、古き良き詩集も読みたくなってくる。

ソースコード

 スクリプト自体よりも、背景知識が足りないと思うことが多い。

man bash

 これが公式ドキュメントだったのだろうか。文法にも詳しく、参考になる。e.g. func() ( .. ; )func { .. ; }の使い分けが活きる場面があった。

終わりに: シェルについての雑感

 良くも悪くも文字列しか無い。

  • コンピネータ(map, fold..)を使った処理が書けない。代わりにawkなどを学ぶことになる。
  • 他の言語をワンライナに使うとき、コードをクオーツで囲む必要がある(シェルの単語分解が邪魔)。
  • xargs -I の文字置換がコマンド置換よりも遅い。xargs -I{} sh -c '<code>' とする必要のある場面が出てくる。
  • where式が無い。here documentsしか無い。

 シェルの真上で専用の言語 (Lispなど) が走るものもあるらしい。検討中。

実装に関する様々なBobの話

 Roguelike Advent Calendar 2018 二日目の記事です。書いているのは、wanna-beというか、素人です。Bob Nystrom氏の発信をまとめただけですが……ご了承いただければと思います。

Bob Nystrom - Is There More to Game Architecture than ECS?

 この動画 が元ネタです。オブジェクト指向の話です。

1. Components (as Capabilities)

 オブジェクトをComponentの組み合わせとして捉えると、(動的な)組み合わせにより、柔軟性が得られます。たとえばキャラは、 AI, Inventory, Item などの組み合わせになります。

public class Item : Component {
    // Itemも、capabilityの組み合わせとして構成する
    val attack: Attack   // 武器の機能
    val defence: Defence // 防具の機能
    val use: Use         // アイテムの機能(読む、飲む、振る、……)
    ...
}

 これで、振ると火を吹くペットの少女を装備して攻撃できますね! (すごい絵面だ)

 Componentは長年のトレンドであり、ADOMの作者さんもはまっています。

2. Type Object Pattern (is a Must)

 Type Objectは、パラメータ依存の領域をオブジェクトにして切り出すパタンです。

public class ItemType : Content {
    val name: Name
    val price: Int
    val attack: Attack
    ...
}

 パラメータ依存の部分を、データ駆動にできるのはもちろん、抽象的なインタフェイスに置換できます。(データ駆動ではなく、Builderなどを使ってハードコーディングしても構いません)。

 定義ファイルでは、他のデータを『継承』する記法をサポートしてもいいでしょう。継承と言っても、データの上書き・拡張ができれば十分です。

:: club
    category = Melee Weapon

Cudgel[s] :: club
    art     = LightBrown \
    attack  = 1-4 wood
    price = 12

 原文よりも限定的で、how to風な書き方をしました。元の説明は比喩的です。

3. Command Pattern (is Great Good)

 Commandは、素朴にはクロージャであるとBobは認識しています

// 本質的には変数のキャプチャ
public class WalkAction( actor: Entity, dir: EDirection ) : Action( actor ) {
    // クロージャを後から .invoke() するのと似ている
    protected override fun perform() {
        ...
    }
    ...
}

 実際には、オブジェクトであるため、文脈に強く、拡張できます。プロパティを持ったり、undo()を足すこともあります。(でも、ローグライクでundoは難しそうです)。

 Actionオブジェクトを作ると、Actorのフィールドから.walk()などの無数のメソッドを抜き出すことができます。また、Actionはドメインをまたいで利用できます。(たとえば、AIとPlayerInputHandlerの両方から)。

 Commandを、how to風に抽象すると……動詞をオブジェクトに変えて独立させると、柔軟性が得られる場合があります。悩んだ時には検討したい項目です。

4. An Iterator as a Game Loop (is Essential)

 以下の記事が基になっています:

 具体的には、Amaranthのゲームループが参考になります。Hauberkのループは、言語機能の制限からか、update関数で頑張っています。(Dartには、fiber, green thread, coroutine などがありません)(たぶん)

Engine-UI Separation

 コードをEngineとUIに分けます。Engineとは、ローグライクの内部状態です。UIは、演出や、入力への対応を行います。(EngineとUIは、ModelView の関係に近いです)。

 複数のUI (CLIGUI) から、同じEngineのコードを使いたいとします。そのために、EngineをUIから分離します。

EngineとUIの関係

 Engineの状態を、ステップ単位で進められるようにします。Engineは、.process()することで僅かにゲーム状態を進め、GameResultを返します。Engineのゲームループは、GameResultを生み出し続けるものと言えます(エニュメレータ)。

 UIがEngineオブジェクトを持ちます。UIは、engine.process()で時間を進めては、進んだ分を演出します。(つまり、ログの表示やActionの演出を行います)。キャラのActionを決めてEngineに渡すこともあります。

ゲームループの実装法

 ローグライクのゲームループは、Actionのプロセッサです。本質的には、

class Engine:
    def __init__(self, scheduler: Iterable[Actor]):
        self.scheduler = scheduler
    def game_loop(self):
        for actor in self.scheduler:
            actor.gain_energy()
            for action in actor.may_take_turn():
                action.perform()
    # game_loop() の内容を、停止・再開できるようにして実装する
    def process(self) -> GameResult:
        pass

 C#では、イテレータブロックを使えば楽です。他の言語でも、fiber, green thread, coroutineなど、別名の似た機能があるかもしれません。無ければ、update関数で頑張って実装します(Hauberk)

Actionを演出する方法?

 特にGUIの場合、 *苦戦* します。(BobのゲームはAsciiベースです)。

 まだ良い方法が思いつかないので、飛ばします。一番肝心な部分ですみません。

 むしろ、どなたか教えてください (T_T) 。EngineとUIを完全に分離したまま、Actionの演出をUIに担当させたいです。(ActionはEngineにあります)。

補足: 『ゲームループ』について

 メジャなのは特殊な定義で、FPSを生み出します。いわば、アプリケーション・レベルのゲームループです。(ゲームアプリのループと呼べば、一般名詞から区別できると思います)。

 今回考えた『ゲームループ』は、用語ではなく、単なる「ローグライク・ゲームのループ」です。Engineレベルのゲームループとも言えます。Engineの時間を入力などから分離した、という意味では、本質はゲームアプリのループと同じかもしれません。

 以上で、この記事の内容は終わります。

References

Bob

Open-Source Roguelike Games

 本記事とは関係がありませんが、参考になりそうなものを引っ張ってきました。

Else

 RogueBasinより、FoVの記事を引っ張ってきました。

 貴重な情報であり、ありがたく思います。日本語では、この手の文書はほぼ無い気がします。(あれば教えてください!)

 やはりCLI寄りですが、 r/roguelikedev も参考になりますね。

終わりに

いつかやりたい/書きたいこと

 ここが難しそう! のリストです。 こうして見ると、ローグライク・ゲームを完成させるのは、中々の栄光だと思います。

 他の方が、どのようなルートでゲーム制作の技術を得たのか、興味が湧きます。(気合だと言われそうですが……)

View
  • キャラのActionの可視化(非常に苦戦しそうです)
  • 影(視界)とフォグ (描画方法で苦戦しそうです)
    • FoWの実装と可視化
    • FoVのOOPな実装と可視化
UI
  • ダッシュやクリック移動のディテール(GUI
    • Behavior : Component の切り替え?
Engine
  • マルチタイルのEntity (e.g. 2x2)
  • セーブ・ロードのディテール(かなり苦戦しそうです)
    • versionと互換性
    • 暗号化
  • スクリプト、Interactableの実装(RPGツクールのイベントに相当するもの)
Editor
  • Tiledのマップにスクリプトを配置(苦戦しそうです)
    • 理想的には、Godotのように、独自のエディタにTiledのマップを表示して、スクリプトを配置していきます
    • Ascii表示のエディタでマップを編集し、ゲームではタイル表示にする人もいるようです
      • 施せられる装飾に限界があると思います
Else
  • Pure ECSの実装の理解(使いません)

以上です

 最後まで読んでいただき、ありがとうございました。

 皆様の記事を読める日を楽しみにしています。

Virtual Input Reimplemented

 コントローラのコード中では、入力を直接扱うことことはせず、仮想入力(VInput)に対して操作を設定する。Nez.UIにもVInputのキットがあるが、細かい(誰も気にしない)問題があった。

 そろそろ、オブジェクト指向の本を読むか、関数型に取り組むと良い頃かも。今なら一部は意味が分かるかもしれない。

再実装

 擬似コードはニュアンス程度のもの。

トップダウンで見た必要物: VirtualButton

 公開レイヤは仮想ボタンであり、単ボタンと方向入力が必要。

  • VSingleButton (例: 選択キー)
  • VDirInput
    • VAxisInput (例: 上下左右キー、左スティック)
    • VEightDirInput (例: numpads(テンキー)、yuhjklbn)

 方向入力には、スティック(AxisInput)とボタン(EightDirInput)の二種類がある。スティックはx, y座標を合成して方向入力を生み出すが、ボタンは二つのキーが押された場合、片方を優先し、もう片方を無効にする。

公開レイヤのインタフェイス: IVButton

 仮想ボタンには、以下のようなインタフェイスを期待する。

// パルスは、いわゆるキーリピートの機能。
// 右方向を時間軸として、パルスはたとえば: |  |||||||| ..
pub trait RepeatPulse {
    fn isPulsing() -> bool; // (正しい英語??)
    fn setRepeatTime( first: f32, multi: f32 );
    fn setPulseLen( len: f32 ); // len > 0 で複数フレームをまたぐ
}
// 実装上は、パルスは包含でもok(ユーザの手がパルスのインタフェイスに届くならok)
pub trait VButton : RepeatPulse {
    fn isDown() -> bool;
    // パルスを抜いた生の値。isRawlyPressed() の方がいいかも。(それでも英語は間違っている気がする)
    fn isPrimPressed() -> bool; // is this primitively pressed?
    fn isPressed() -> bool { self.isPulsing() || self.isPrimPressed() }
    fn isReleased() -> bool;
}
// 方向ボタンのインタフェイスは、VValueButton<EDir>
pub trait VValueButton<T> : VButton {
    fn valueDown() -> T?;
    fn valuePressed() -> T?;
}

公開レイヤの実装を薄くする

 後は実装が問題となる。ここで、実装の基礎の部分と、公開用のオブジェクトは切り離していい。部品を使って公開用オブジェクトを作る形を取る。

 PrimNodeを継承によって拡張する。VButtonはNodeを使って実装するが、なるべく薄い層にする。

ボトムアップの実装

 VButtonのことは忘れ、Nodeを組み立てる。

PrimとContainer

 最も基本的なNodeをPrimNodeと呼ぶことにする。キーボードやマウス、ゲームパッドの入力が含まれる。コンテナも用意して、拡張に備える。

pub trait PrimNode {
    fn isDown() -> bool;
    fn isPrimPressed() -> bool;
}
pub trait ContainerNode<T> : PrimNode where T: PrimNode {
    fn components() -> Iter<BufNode>;
    fn nodePrimPressed() -> T? {
        self.components.find{ it.isPrimPressd() }
    }
}
impl<T> PrimNode for T where T: ContainerNode {
    fn isDown() -> bool {
        self.components.any{ it.isDown() } 
    }
    fn isPrimPressed() -> bool {
        self.components.any{ it.isPrimPressed() }
    }
}

 RepeatPulseとisReleased()はNodeから抜いた。Buttonを作るときに、Nodeをラップする形でそれらの機能を追加する。

BufNode

 Buttonに必要だった、連続入力フレーム数を持つNode。

pub trait BufNode : PrimNode {
    pub buf() -> u32; // フィールドに持つ前提
}
// 優先入力のBufNodeを抽出してくれるコンテナ
pub trait BufSelecterNode<T> : ContainerNode<ButtonNode<T>> {
    fn nodeDown() -> T? {
        self.components()
            .where{ it.isDown() }
            .minByOrNone{ it.buf() }
    }
}

 バッファを作らず、新たな pressed キーの入力を優先して、上書きする方法もある。問題は、最近押されたキーの入力が途切れて、他に.isDown()な複数のNodeあるとき、どれを優先すべきか分からないこと。(若いIDとかで選んでも良いが、厳密にしたかった)。

ValueNode, ValueBufNode

 Axis入力のNodeは、(-1, 0, 1)のいずれかの値を取る。方向入力のNodeは、EDir?を返す。

pub trait ValueNode<T> : PrimNode {
    fn value() -> T;
}
type ValueBufNode<T> = ValueNode<T> + BufNode<T>;
// 優先入力のValueBufNodeの値を抽出してくれる
pub trait ValueBufSelecterNode<T> : BufSelecterNode<ValueBufNode<T>> {
    fn valueDown() -> T {
        self.nodeDown()?.value() ?? default(T)
    }
    fn valuePressed() -> T {
        self.nodePressed()?.value() ?? default(T)
    }
}

 これを基に、PrimAxisNode、IntAxisNode, EDirNodeなどを作って、ラップしてボタンにする。

まとめ

やったこと

 上層(VButton)から下層(Node)を分離し、下層を拡張した。下層をラップする形で上層を作った。結果、コードは4つの層に分かれた:

general (一般名詞) base (共通実装) special (特殊オブジェクト)
Node PrimNode系 BufNode系
VButton RepeatPulse VSingleButton, VDirInput

how to 化

 アーキテクチャ一般、共通、特殊に分けて捉えると、層の分け方が綺麗になるかもしれない。

+------------general------------+    +------------another------------+
|                               |    |                               |
|   +---+  +---+  +---+ +---+   |    |   +---+  +---+  +---+ +---+   |
|   | s |  | s |  | s | | s |   |    |   | s |  | s |  | s | | s |   |
|   +---+  +---+  +---+ +---+   |    |   +---+  +---+  +---+ +---+   |
|     |      |      |      |    |    |     |      |      |      |    |
|   +-----------------------+   |    |   +-----------------------+   |
|   |          base         |   |    |   |          base         |   |
|   +-----------------------+   |    |   +-----------------------+   |
|                               |    |                               |
+-------------------------------+    +-------------------------------+
難しさ

 継承の仕様上、コードの上ではインタフェイスの拡張共通実装の拡張区別しにくいのが難点だった。(非明示的なメタ情報になる)。

 上の図で、多少区別がマシになるが、新たなパラダイムが出てくるのではと予感した。

備考

書いていない部分

 BufNodeの更新方法。親が責任を持って更新する。

運用法

VInputをゲーム全体でシェアする

 共有しても問題無いと思う。

VInputをUIフレームワークに流し込む

 流し込めない設計のUIは良くない。

絶対に安全な入力の取り扱い

 isPressed.consume()することで、確実に無限ループを避けられる。シーケンスの移行時などに、強制的に全入力を.consume()すれば安全なはず。