2020/11/09
というわけで、前回からかなり間が空いてしまいましたが、気まぐれと勢いで作っているタイピングゲームの制作日記(その2)です。
前回の記事はこちら
今回は「入力対象となる文字列の取得・設定」編です!
前回はスクリプト上で決め打ちだったので、タイピングゲームでも何でもありませんでした。
文字列(言葉)の管理と取得、入力すべきアルファベットの認識までやってみます。
ちなみに Unity とタイトルに入れつつ、Unity っぽいことはあまりやっておりません。
C# のスクリプト作成くらいでしょうか。
どうしても裏側の話題になってしまうので、興味がある方だけお付き合い下さい。
入力対象とする文字列の取得
当たり前ですが、入力対象となる文字列は毎回ランダムに設定する必要があります。
平仮名の50音からランダムで取得して組み合わせても言葉にならないので、どこかで言葉をまとめて管理しないと厳しいです。
どのようなタイピングゲームにするのかにも寄りますが、一般的な単語や文章、ことわざ、四字熟語何かが設定されていることが多いです。
キャラゲーよりのタイピングソフトは台詞なんかも混じっていたりしますね。
で、当初は YAML ファイルに追加していくことを考えましたが、言葉が多くなってくると管理が大変になります。
最低でも「画面への表示名」と「入力する文字列」を設定する必要があります。
また、「単語ごとのカテゴリ」とか「入力数」「難易度」とかも設定できるようにしておくと後々役に立ちそう。
・・・という場合、データベースで管理するのが一番よさそうです。
取得時に条件指定やグルーピングが出来るので、言葉の選定に無駄なロジックを挟まなくてもいけそうですね。
本ブログは「さくらのVPS」上に立てており、WordPress を使用しています。
なので VPS 上の MySQL サーバに専用のデータベースを作成し、言葉管理用のテーブルを作成します。
CREATE TABLE words ( id integer not null AUTO_INCREMENT PRIMARY KEY, target_word varchar(200) not null, logical_word varchar(200) not null ) ENGINE=InnoDB;
主キーはないよりあったほうが管理しやすいでしょう。
「target_word」が入力対象とする平仮名、「logical_word」が画面上に表示する言葉の文字列です。
適当に単語を挿入しておきます。
INSERT INTO words VALUES (NULL, 'かんがるー', 'カンガルー'); INSERT INTO words VALUES (NULL, 'きりん', 'キリン'); INSERT INTO words VALUES (NULL, 'うさぎ', 'ウサギ'); INSERT INTO words VALUES (NULL, 'あざらし', 'アザラシ'); INSERT INTO words VALUES (NULL, 'はわいもんくあざらし', 'ハワイモンクアザラシ'); INSERT INTO words VALUES (NULL, 'ちちゅうかいもんくあざらし', 'チチュウカイモンクアザラシ');
MySQL サーバと通信して取得する過程は長いので割愛しますが、「Unity MySQL」とかでぐぐると結構ヒットするので問題ないはず。
自分は「WWW クラスでサーバにリクエストを送り」「PHP から PDO で SQL を実行し」「レスポンスの JSON をパースして保存」という手順で実装しました。
C# 単独で MySQL に接続する dll もあるっぽいので、そのあたりはお好みでよさそうです。
1つハマったこととして、データベースから取得したマルチバイト文字が16進表記になってしまう問題が発生しました。
こういう時は大抵文字コードにブレがあったりするものですが、PDO での接続やデータベース・テーブルは UTF-8、PHP の内部エンコーディングも UTF-8 です。
$dsn = 'mysql:host=host;dbname=dbname;charset=utf8mb4'; $pdo = new PDO($dsn, $this->db_user, $this->db_password);
どうも PDO インスタンス生成時の dsn がよろしくなかったようで、charset に「utf8mb4」を指定したら解決しました。
「utf8」では16進になってしまいます。
また、MySQL をあまり使っていない人は「UTF-8」と指定しがちですが、小文字ハイフンなしの「utf8」が正しい指定です。
dsn に無効な charset を渡すと無視される(と思われる)ので注意しましょう。
あとはひたすら台詞を挿入していくだけなのですが、これが非常に面倒です。
ひとまず動作確認に必要な分だけ突っ込んでいますが、本格的に追加していく際は方法を考える必要がありそうです。
とはいえ「平仮名」と「表示名」で分けて保存する関係上、気合で用意するしかないかもしれません。
実際に入力するアルファベットの表示
これで「表示名」や「入力する文字列」は取得できます。
が、画面上に入力するアルファベットが表示されないのは不親切であり、そのアルファベットをスクリプト側で把握していなければ、入力時のマッチングも行えません。
そこで、平仮名50音と「入力するアルファベット」を対応付ける YAML ファイルを作成します。
単に「入力すべきアルファベットを入れる」の場合、複数通りの打ち方のある文字の制御が厳しくなってしまいます。(「ぁ」は「xa」か「la」など)
後々のことを考えるとアルファベットで直接定義するのは望ましくないです。
また、対応付けのキーとなる平仮名も、そのまま「あ」とか使用するのは避けたいところ。
もし文字コードが異なる環境で動作させた場合、平仮名が文字化けして対応付けが出来ないことになると思われます。
ということで、キーは50音を16進数の文字コードへ変換したものを使います。
実際に作った YAML がこちらです。
E38181: xa,la E38182: a E38183: xi,li E38184: i E38185: xu,lu E38186: u E38187: xe,le E38188: e E38189: xo,lo E3818A: o E3818B: ka,ca E3818C: ga E3818D: ki E3818E: gi E3818F: ku,cu E38190: gu E38191: ke E38192: ge E38193: ko,co E38194: go E38195: sa E38196: za E38197: si,shi E38198: zi E38199: su E3819A: zu E3819B: se E3819C: ze E3819D: so E3819E: zo E3819F: ta E381A0: da E381A1: ti E381A2: di E381A3: xtu,ltu E381A4: tu E381A5: du E381A6: te E381A7: de E381A8: to E381A9: do E381AA: na E381AB: ni E381AC: nu E381AD: ne E381AE: no E381AF: ha E381B0: ba E381B1: pa E381B2: hi E381B3: bi E381B4: pi E381B5: hu,fu E381B6: bu E381B7: pu E381B8: he E381B9: be E381BA: pe E381BB: ho E381BC: bo E381BD: po E381BE: ma E381BF: mi E38280: mu E38281: me E38282: mo E38283: xya,lya E38284: ya E38285: xyu,lyu E38286: yu E38287: xyo,lyo E38288: yo E38289: ra E3828A: ri E3828B: ru E3828C: re E3828D: ro E3828E: xwa E3828F: wa E38293: nn E383BC: '-'
50音を濁音・小文字込みで網羅し、伸ばし用のハイフンを入れたマッピングファイルです。
カンマ区切りのものは複数通りの入力が可能なものです。
データベースから取得した平仮名を1文字ずつ判定し、上のマッピングファイルに従って「どのアルファベットを打つか」を設定していきます。
平仮名を16進へ変換する必要があるので、以下のようなユーティリティクラスを作成してみました。
using System; using System.Collections.Generic; using System.Text; public class EncodeUtility { /// <summary> /// 指定した文字列を16進数コードに変換し、文字ごとに配列へ格納して返す /// </summary> public static List<string> convertHex(string text) { List<string> hexes = new List<string>(); foreach (char c in text) { // 入力文字列のバイトコード取得 byte[] text_bytes = Encoding.UTF8.GetBytes(c.ToString()); // バイトコードを16進数に変換 string hex = BitConverter.ToString(text_bytes); hex = hex.Replace("-", ""); hexes.Add(hex); } return hexes; } }
平仮名1文字ずつが List
(長くなるので関数だけ記載します)
/// <summary> /// 入力対象の16進コード配列をアルファベット文字列に変換する /// 候補がカンマ区切りで複数ある場合、最初のアルファベットを設定する /// </summary> public string getInputAlphabet(List<string> hexes) { string alphabet = ""; foreach (string hex in hexes) { // マッピングから16進に一致する値を取得 string mapping_value = alphabetMapping.getValue(hex); // 候補が複数ある場合は最初のアルファベットを取得 int separate_index = mapping_value.IndexOf(SEPARATER); if (0 < separate_index) { string value = mapping_value.Substring(separate_index); alphabet += mapping_value.Replace(value, ""); } else { alphabet += mapping_value; } } return alphabet; }
かなり端折っていますが、alphabetMapping は List 型で、yaml からパースしたマッピングのキーと値を管理します。
YAML に関するあれこれは以下の記事で書いているので、そちらをご参照下さい。
マッピングの List から16進文字コードを元にアルファベットを取得、IndexOf() でセパレータごとに区切り、最初の値を設定します。
あとはアルファベットを全部結合したものを返すだけです。
これでアルファベットの設定はできますが、複数通りの入力への対応や、小文字が入る場合の入力方法 (「しゃ」なら「sya」「sixya」など) が出来ていないので、完全とは言い難い代物です。
そのあたりの解決がタイピングソフト実装の最難関かなーと思っています。
まとめ
かなり適当な記事になってしまいましたが、文字列の取得と設定について考えてみました。
今回までで、「入力欄生成」「入力文字のマッチング」「文字列の設定・表示」まで完成しています。
細かい部分はさておき、やっとそれっぽくなってきました!
次回はゲームっぽく、プレイヤーキャラクターと敵キャラクターの制御あたりを作ろうかと考えています。
単に言葉だけを打つなら代わりはいくらでもあるので、「ゲームにする」のは重要ですね。