从自制游戏中提取出日语输入组件,作为 Godot 4 用的 addon 公开了
自作ゲームから日本語タイピング部品を切り出して、Godot 4用のaddonとして公開しました
はじめに
自作のタイピングガンアクション「TypeGunner」(Godot 4)で使っていた、日本語をローマ字で打つための入力部品を、別のゲームでも使えるように切り出して公開しました。
- Godot 4.6系、typed GDScript
- MIT License、v0.1.0
- 外部依存なし
この記事では、何ができるのかを簡単に紹介したあと、ゲームから切り離すときに考えた設計の話と、切り出しの手順を書きます。導入方法だけ知りたい場合は、READMEの「導入」を見てください。
何ができるか
ゲーム側は「タイプ対象のNodeに、かなを添えて登録する」だけです。
TypingEngine.register_target(enemy, {"kanji": "日本語", "kana": "にほんご"})
あとはプレイヤーがキーを打つと、addonがローマ字の候補と照らし合わせ、1文字ごと・単語を打ち終えたときにsignalで知らせてくれます。
ローマ字は1通りではなく、よくある打ち方をまとめて受け付けます。たとえば次のような入力がどれも正解になります。
| かな | 受け付ける打ち方(一部) |
|---|---|
| し |
shi / si / ci
|
| ち |
chi / ti
|
| ふ |
fu / hu
|
| しゃ |
sha / sya / shixya(分解して打つ) |
| きって |
kitte / kixtsute / kiltute など |
| かんじ |
kanji / kannji / kaxnji
|
| ティー |
thi- / texi-
|
カタカナはひらがなとして扱うので、コーヒー のような語もそのまま登録できます。
ほかに、次の部品が入っています。
-
TypingDisplay.tscn: かなと、入力済み・次の1文字・残りを色分けしたローマ字を表示する部品 -
KeyboardDisplay.gd: JIS/US配列のキーボードを描き、担当指の色と次のキーを示す練習用の部品 -
WordPicker.gd: 語彙から出題語を選ぶstatic関数
なぜ切り出したか
次に作るゲームでも日本語タイピングを使いたいと考えたのがきっかけです。コピーして使うこともできますが、ローマ字の打ち方の修正(「ん」の扱いなど)を両方に入れる手間を考えると、1か所で直して両方に配るほうが楽です。
そこで「1 addon = 1 リポジトリ」とし、ゲーム側は addons/typing_core にgit submoduleで取り込む形にしました。
設計で決めたこと
依存は「ゲーム → addon」の一方向だけ
切り出すにあたって、addonが知っていてよいことを次の範囲に絞りました。
- どのNodeが、どのかなを打たせる対象か
- いま何文字目まで打てているか、どの対象をfocusしているか
ダメージやスコア、撃破演出は全部ゲーム側の仕事です。addonはsignalを出すだけで、受け取ってどうするかには関与しません。
signal word_completed(unit: Node)
signal char_typed(unit: Node)
signal mistype(unit: Node)
signal no_target_hit
signal focus_changed(unit: Node)
対象の寿命はNodeに任せる
タイプ対象はシーンツリー上のNodeそのものです。登録時に tree_exiting へ接続しておき、Nodeが消えたら自動で登録を外します。
var exit_callable: Callable = _on_target_tree_exiting.bind(unit)
if not unit.tree_exiting.is_connected(exit_callable):
unit.tree_exiting.connect(exit_callable, CONNECT_ONE_SHOT)
おかげでゲーム側は、敵を倒したら queue_free() するだけでよく、解除のし忘れで「画面にいない敵が打てる」ことが起きにくくなりました。なお、ツリーに入れていないNodeには tree_exiting が来ないので、その場合だけ自分で unregister_target() します。
「最初の1文字」でfocusする
画面に複数の対象があるとき、何もfocusしていない状態で打った1文字目が候補に合う対象のうち、先に登録したものをfocusします。以降は、TabかEscでfocusを外すまで、その対象だけに入力が入ります。
この規則だと、先頭の文字が同じ語が並んだとき、後から出た語を選べません。そこで出題側の WordPicker.pick() には、画面上の語の先頭文字を除外する引数を持たせました。条件で候補が空になったら先頭文字の条件だけを外し、それでも空なら全語から選ぶ、という順で必ず1語を返します。
ローマ字変換は状態を持たない部品に分けた
当初は入力判定とローマ字の候補生成が1つのAutoloadに入っていましたが、公開前に候補生成を RomajiConverter.gd に分けました。かな文字列を受け取って、入力単位ごとの候補を返すだけの、状態を持たない部品です。
# 「かんじ」を区切った結果
# か ["ka", "ca"] / ん ["nn", "xn", "n"] / じ ["ji", "zi", "nji", "nzi"]
変換表は kana_romaji.json(かな → ローマ字の配列)に外出ししてあり、打ち方を足したいときはJSONを編集すれば済みます。
ただ「っ」と「ん」だけは、表ではなくコードで決めています。どちらも前後のかなで正解が変わるからです。
- 「っ」: 次のかなの子音を重ねる入力(
kitte)を受け付ける。末尾の「っ」はxtsuなどの単独入力だけ - 「ん」: 次のかなが母音・や行・な行で始まるときは、
n1つだと次のかなと区別できないのでnn/xnを求める(「きんえん」の「ん」はnnかxn)。末尾の「ん」はnだけ - 「ん」を
n1つで打てる位置では、続けてもう1つnを打つ入力(kannji)も受け付ける
状態を持たないので、Autoloadを立てずにこの部品だけ試験できます。こうした境界ケースは tests/run_romaji_converter_tests.gd に固定しました。
表示は打った綴りに追従する
TypingDisplay は、入力済みの文字に、残りの区切りの先頭候補をつなげて表示します。「にほんご」を nihonn まで打つと、表示は nihonn + go に変わり、プレイヤーが選んだ綴りに合わせて次の1文字が示されます。
候補の並び順は表示に直結するので、たとえば「ん」の後ろに n を足した候補は元の候補より後ろに置いています。先に置くと「せんせい」が sennnsei と表示されてしまうためです。
置き場所に依存しない読み込み
addonはどこに置かれるかわからないので、JSONや他のscriptは、自分のscriptと同じフォルダから読みます。
var base_dir: String = get_script().resource_path.get_base_dir()
var converter_script: GDScript = load(base_dir.path_join(ROMAJI_CONVERTER_FILE)) as GDScript
また class_name と preload は使っていません。class_name を使わないので、取り込み先のゲームのグローバルなクラス名と衝突しません。
addon単体で試験できるようにした
tests/run_tests.sh は、一時フォルダに最小のGodotプロジェクトを作ってaddonを複製し、TypingEngine をAutoload登録してから試験を走らせます。ゲームに取り込まなくても、addonのリポジトリだけで回帰試験ができます。
GODOT=/path/to/godot sh tests/run_tests.sh
切り出しの手順
いきなり別リポジトリに移すのではなく、段階を踏みました。
- TypeGunnerの中で、タイピング関連のファイルを
addons/typing_core/に集め、ゲーム固有のAutoloadへの参照をなくす - 3か所に散っていた出題語の抽選を
WordPicker.gdにまとめる(旧実装と3000回比べて結果の一致を確認) -
git subtree splitでそのフォルダの履歴ごと新しいリポジトリへ移す - TypeGunner側の
addons/typing_coreをsubmoduleに置き換える - 公開前の整理として、ローマ字変換の分割、キー配列のJSON化、カタカナと外来語のかな(「てぃ」「ゔぁ」など)の追加、MIT Licenseと
plugin.cfgの追加を行う
1の段階でゲームの中のまま依存を断っておいたので、3と4はファイルを移すだけで済みました。移した後もTypeGunnerで動作を確認しています。
制限
v0.1.0の時点で、次のことはできません。
- 漢字の入力。
kanaに変換表にない文字を含めると、その語は打てません(登録時に警告が出ます) - 記号や数字の入力。受け付けるキーは
a〜zと-(長音)だけです -
WordPickerに渡す語彙のromajiは、自分で書く必要があります。先頭文字と長さの判定に使います
おわりに
日本語タイピングのゲームを作るとき、ローマ字の打ち方の揺れや「ん」「っ」の扱いは地味に手間がかかるところです。Godotで同じようなゲームを作る方の役に立てばうれしいです。導入手順やAPIはREADMEにまとめています。
来源:zenn · 游戏开发社区 · zenn.dev