2013年12月18日水曜日

[Glaeja] ♫色んな言葉で

キミに愛を告げるシチュエーション別チュートリアルエントリです。

今回のシチュエーションは『言葉を変える』です。

『Glaeja』は様々なシステム情報を文字列として表示できますが、この表現を違う言葉で表したい、という要求があります。また、システム情報に応じてセリフのように表示させる文字列を変えたい、ということもあるでしょう。

こうした『言葉を変える』場合に頻出する「@...@」について、いくつかレシピを公開したいと思います。


今回もシチュエーションを以下のように細分化します。
  1. 数値で表される情報を言葉に変える
  2. 言葉で表される情報を別の言葉に変える
  3. 大量の言葉から選ぶ
  4. 言葉を差し込む
  5. 長さが不定な言葉を扱う

1. 数値で表される情報を言葉に変える


『Glaeja』のエスケープキャラクタにおいて、多くのシステム情報は数値として展開されます。

例えば、カレンダー情報での「年月日」は当然数値ですし、曜日も「$f$」で数値として展開されます。これらは「バー」や「サークル」のレベルとして用いられることもあれば、「テキスト」等で数値そのままで表示されることもあります。

このとき、(´-`).oO(数字じゃなくって言葉で表示したいなぁ) と思うことがあります。

そういう場合、「@.../.../\m@」という前方文字置換エスケープキャラクタを使います。

このエスケープキャラクタの機能を平たく言うと、『複数の言葉から1つ選んで表示する』になります。
.../.../」で示された文字列にある複数の言葉のうち、スタックからポップした番号のもの以外が削除される、というものです。

例えば、「カレンダーの『月』名を数字ではなく和名(睦月、如月、…)で表示する」とします。

この場合には、
  1. カレンダーの『月』を数値としてスタックにプッシュする
  2. 月和名をカレンダー順にコンマ区切りで全部書く
  3. @.../.../\m@」で現在『月』の和名だけが残り、他は削除される
という手順で文字列を作ってやります。上記手順をエスケープキャラクタ文字列にしてやると、
  1. $M$@p@
  2. ,睦月,如月,弥生,卯月,皐月,水無月,文月,葉月,長月,神無月,霜月,師走
  3. @0/-1/\m@
となります。実際に「テキスト」の表示文字列に書く際は全部を1行に繋げて書いてください。


この例では、この他に文字列がない前提で「@p@」や「@0/-1/\m@」という構文を使っていますので、他に文字列がある場合には[文字列開始位置]や[文字列長]指定を適宜記述して、正しく該当部分文字列を指定してください。

このエスケープキャラクタ文字列で注意する点は、『2. ,睦月,如月,...』の部分です。
ここで先頭にいきなりコンマが記述されています。これは「@.../.../\m@」では、選ぶ言葉を「ゼロ番目、1番目、...」と数えるため、存在しない『ゼロ月=ゼロ番目』が『睦月』に対応してしまう、というのを防ぐために先頭にコンマを入れて『睦月』を『1月=1番目』にズラしているのです。

この@.../.../\m@」で数値を文字列に変換する、というテクニックは、『Glaeja』のエスケープキャラクタ文字列で最頻出のものでして、
  • 数値を言葉ではなく、別の数値に変換したりして計算式を記述しないで済ましたり、
  • 結果である言葉を、そのまま表示するのではなく「イメージ」等でのファイル名にしたり、
色んな用途で幅広く用いられています。是非とも使い方を覚えてください。


2. 言葉で表される情報を別の言葉に変える


システム情報のいくつかは数値ではなく言葉に展開されます。

例えば、バッテリー充放電状態「#S#はロケールが英語なら[unknown, charging, ...]、日本語なら[不明, 充電中, ...]という言葉(のいずれか)に展開されますし、モバイルデータ通信の接続状態「!E!はロケールが英語なら[disconnected, connecting, ...]、日本語なら[切断, 確立中, ...]という言葉に展開されます。

これらに対して、(´-`).oO(別の言葉で表示したいなぁ) と思った場合、数値ではありませんので「@.../.../\m@」を使うことができません。

こうした場合には、「@.../.../\M@」を使って言葉を数値に変換してから「@.../.../\m@」を使います。

このエスケープキャラクタの機能を平たく言うと、『ある言葉が選択肢のどれと一致してるか番号を表示する』になります。「.../.../」で示された文字列をコンマで区切り、その先頭(ゼロ番目)にある言葉が、残りの1番目以降のどの言葉と一致するか、という番号に展開して表示する、というものです。

例えば、「カレンダーの『午前/午後』をひらがな(ごぜん/ごご)で表示する」場合を考えます。

カレンダーの『午前/午後』は、エスケープキャラクタ「$a$」によって、「テキスト」等の[ロケール]が[日本語]なら「午前/午後」と、[英語]なら「AM/PM」と展開されます。

ですので、[ロケール]を英語にした上で
  1. $a$」が「AM」か「PM」のどちらと一致するか、「@.../.../\M@」を使って番号に展開
  2. その番号をスタックにプッシュ
  3. @.../.../\m@」で別の言葉に置換して表示
という手順で文字列を作ってやります。上記手順をエスケープキャラクタ文字列にしてやると、
  1. $a$,AM,PM@0/-1/\M@
  2. @p@
  3. ,ごぜん,ごご@0/-1/\m@

1. で、判定したい言葉となるエスケープキャラクタを先頭に書き、その後にコンマ区切りで選択肢の言葉を連ねて書きます。
1. の書いた「$a$,AM,PM」という文字列全体が判定結果の数値に置換されますので、それを 2. でスタックにプッシュします。
3. でスタックからポップした判定結果を基に対応するひらがなを表示しています。

ここで注意する点は、3. の先頭にコンマが入っているところです。
@.../.../\M@」では、「選択肢に一致する言葉がなかった場合には結果として"0"(ゼロ)が展開される」ため、「$a$ == "AM"」なら"1"が、「$a$ == "PM"」なら"2"が、判定結果となります。
そのため、 「@.../.../\m@」で1番目・2番目が対応するよう、先頭にコンマを入れてズラしているわけです。

逆に、この「選択肢に一致する言葉がなかった場合には結果として"0"(ゼロ)が展開される」を使って、選択肢以外の場合を一括的に取り扱うことも可能です。

バッテリー健康状態「#H#」は、[ロケール]が英語だと[unknown, good, overheat, ...]という言葉に展開されるのですが、これを「[good]とそれ以外」に分けて表示を変えるには、
  • #H#,good@0/-1/\M@@p@なんか調子悪い,大丈夫@0/-1/\m@
と、[good]だけを選択肢に入れておき、[good]以外をゼロ番目にしてしまえば良いのです。


3. 大量の言葉から選ぶ


上述した「@.../.../\m@」と「@.../.../\M@」が使えれば、大抵の場合はなんとかなります。しかし、「出来るけどちょっとしんどい」という場合があります。

例えば、『セリフを喋るマスコット』スキンを作ろうとする場合、セリフが長くて「テキスト」表示文字列に入力するのがつらい、とか、天気スキンで概況文を自作のものに置き換えたいんだけど121個もあって表示文字列がパンパンだぜ、とかですかね。

こうした場合には、「@.../.../\m@」で文字列中から言葉を選ぶのではなく、「@.../.../\f@」を使って別に用意したテキストファイルから言葉を選ぶことができます。

このエスケープキャラクタの機能は、『テキストファイルから1つ行を選んで表示する』になります。
.../.../」で示された文字列をファイル名とするテキストファイルから、スタックからポップした番号の行を取り出して表示する、というものです。行番号の数え方は、先頭行が「ゼロ行目」で以降1行目、2行目となります。

例えば、曜日に応じて以下のようなセリフを表示したいとします。
  • 日曜日:日曜日ですよ、明日に備えてゆっくりしましょう。
  • 月曜日:さぁ月曜日です、今週も張り切っていきましょう。
  • 火曜日:火曜日です、まだ先は長いですが気を抜かずに。
  • 水曜日:折り返し地点まで来ました、水曜日です。
  • 木曜日:木曜日です、あと2日の辛抱ですよ。
  • 金曜日:今日さえ、金曜日さえ乗り切れば…
  • 土曜日:おめでとう、土曜日です。さぁ遊びに出かけましょう!
これらを「$f$」を使って表示し分けるのですが、全部を表示文字列に入力するのもダルいですし、ただでさえ見通しの悪い「@...@」がさらに見難くなってしまいます。

そこで、以下のようなテキストファイルを作成し、「com.gmail.kanitawa.glaeja/images/」以下に適当な名前で保存してやります(ここでは例として"phrases.txt"とします)。


上図では"TeraPad"を使っているので行番号表記が"1"始まりとなっていますが、先頭行にゼロに対応するセリフを書いてください。
文字コードには「BOMなしUTF-8(TeraPadだと"UTF-8N")」を、改行コードは「LF(Ctrl-N)」を使用してください。

「テキスト」レイヤーの表示文字列を以下のように設定することで、曜日ごとのセリフが表示されます。


テキストファイルを「com.gmail.kanitawa.glaeja/images/」以下のサブフォルダ内に保存した場合には(ここでは"sub"という名前のサブフォルダとします)、
  • $f$@p@sub/phrases@0/-1/\f@
とサブフォルダからのパス名を含めてやればテキストファイルが認識されます。


4. 言葉を差し込む


これはちょっとシチュエーションがわかりにくいのですが、
  • 同じ言葉を文章の異なる場所に挿入する
  • 異なる言葉を文章の同じ場所に挿入する
という2つの場合を考えてみます。

前者の例として、時間帯によってセリフが変わる『時刻を喋るマスコット』スキン、というものを考えます。
  • 朝(6~11時):「おはようございます! ただいま??時??分です。」
  • ランチタイム(11~13時):「いまお昼の??時??分です。お昼ごはんにしましょうよ。」
  • 昼(13~17時):「??時??分です。お仕事がんばって!」
  • 夕(17~20時):「お疲れ様でした、??時??分です。」
  • 夜(20~23時):「??時??分です、明日に備えて早めに休みましょう。」
  • 深夜(23~翌6時):「え~、夜中ですよ…時間とかイイからもう寝ましょうよ~」
という感じで時間帯に応じて喋らせるとします(ただし深夜だけは時刻を喋らない)。

文字列の組み立てとして、
  1. 時間「$H$」を「@.../.../\g@」で時間帯に分ける
  2. セリフをテキストファイルから「@.../.../\f@」で取り出す
  3. 取り出したセリフに時刻を挿入する
という方針で進めると、1. と2. はセリフのテキストファイルを"phrases.txt"とすると、
  1. $H$@p@6,11,13,17,20,23@0/-1/\g@@p@
  2. phrases@0/-1/\f@
とすれば良いでしょう。

1. では「23~翌6時」が区間外なのでゼロとして展開されることに注意してください。

で、3. をどうやって実現するかですが、まずセリフのテキストファイルを以下のように作成してやります。


セリフ中の「??時??分」の部分が「***」に書き換えられていることに注目してください。「***」以外の文字列でも構いませんが、セリフ文章に出てこない文字列を使ってください。

そして、この「***」を「@.../.../\S@」を使って「$H時m分$」に置換してやるのです。
文字列にしてみると、
  1. ,***,$H時m分$@0/-1/\S@
こうなります。
先頭のコンマの前には 1. 2. で展開された「***」入りセリフがありますので、そこの中の「***」を「$H時m分$」に置換してやるのです。

@.../.../\S@」では、探索する文字列が見つからなかった場合(上記だと「23~翌6時」)には、探索する文字列と置換する文字列を削除するだけですので、「23~翌6時」にも正しく動作します。




さて、後者である「異なる言葉を文章の同じ場所に挿入する」の例として、セリフ中のシステム情報がランダムで変わるマスコットスキン、を考えてみます。

要するに、
  • パターン1:「お兄ちゃん、今日は??月??日だよっ!」
  • パターン2:「お兄ちゃん、電池は残り??%だよっ!」
のように、『お兄ちゃん、***だよっ!』というテンプレート文の「***」部分に差し込むシステム情報がランダムで変わる、という感じです。

これを実現するため、今まで上げてきた作例を元にしてテキストの組み立てを考えると、
  1. 0, 1]のいずれかとなる乱数を発生させてスタックにプッシュ
  2. お兄ちゃん、***だよっ!」を出力
  3. 今日は$M月d日$, 電池は残り#P'%'#]から「@.../.../\m@」で1つ選ぶ
  4. 選んだものを「@.../.../\S@」で「***」と置換
みたいな手順を考えつくんですが、これは上手くいきません。

理由は、3. の箇所で選択肢となる[今日は$M月d日$, 電池は残り#P'%'#]という文字列が、日時やバッテリー残量によって長さ不定であるため、「@.../.../\m@」の該当部分文字列の長さ指定部を上手く記述できないからです。

こういった「長さ不定」となる文字列を該当部分文字列として指定するには、「@0/-1/...@」のように「文字列全体の先頭まで」を意味する「-1」を長さ指定に使います。
しかし、この場合では、後で「@.../.../\S@」を使うために 2. の箇所で「お兄ちゃん、***だよっ!」が前に出力されているおり、「-1」を使うと「お兄ちゃん、***だよっ!」も該当部分文字列に含まれてしまうため、上手く動作しません。

これに対する解決策はいくつか考えられるのですが、ここでは「テンプレート文と挿入位置が固定」という特徴を利用した方法を2つ提示します。

1つめは、「リッチテキスト」を使う方法です。

「リッチテキスト」では、各文字列群においてそれぞれの文字列は独立ですので、
  • 1つめの文字列:お兄ちゃん、
  • 2つめの文字列:2@p@rand@x@今日は$M月d日$,電池は残り#P'%'#@0/-1/\m@
  • 3つめの文字列:だよっ!
とした場合、2つめの文字列の「@0/-1/\m@」の長さ指定「-1」に1つめの文字列が含まれることはありません。
よって、このテキストは正しく動作します。

この「リッチテキスト」を使う方法は、文字列の表示が「1行」で構わない場合にはとても楽チンな方法です。
ただし、文字列を「マルチラインテキスト」で複数行に改行して表示したいや、「パステキスト」で曲線上に表示したい場合には、文字列を各群に分割できないので使用することができません。

2つめは、@.../.../\t@」を使って文字列を組み換える方法です。

「テキスト」レイヤー等の分割できない表示文字列でテキストを組み上げる際に問題となるのは、要するに『選択肢となる文字列より前に別の文字列があるせいで「@0/-1/\m@」が使えない』ということですから、
  1. 一番最初に「@0/-1/\m@」で文字列を選択して
  2. 後から文字列を先頭に移動
すれば良いわけです。

この『後から文字列を先頭に挿入』するのに「@.../.../\t@」を使います。

テキストにすると、
  1. 2@p@rand@x@今日は$M月d日$,電池は残り#P'%'#@0/-1/\m@
  2. お兄ちゃん、だよっ!@4/6/\t@
となります。

2. で記述している「@4/6/...」の部分が「お兄ちゃん、」を指定しており、この部分を文字列全体の先頭に移動するわけです。


この『@.../-1/...@」を使う部分を最初におこない、後から文字列を先頭に移動させる』というテクニックは、頻出するものではありませんが、知っていると便利なテクニックです。


5. 長さが不定な言葉を扱う


前項では、言葉を差し込まれるテンプレート文の長さと挿入位置が固定だったのですが、それが固定されていない場合にはどうしたら良いでしょうか? 「異なる言葉を文章の異なる場所に挿入する」ような場合ですね。

例えば、セリフ中のシステム情報がランダムで変わるマスコットスキンでセリフもランダムに変化する、というのを考えてみます。

つまり、パターンは前項と同じ2つ[今日は$M月d日$, 電池は残り#P'%'#]として、差し込まれるテンプレート文が、
  • パターンA:「お兄ちゃん、***だよっ!」
  • パターンB:「もうっ、***だってば~」
  • パターンC:「あら、***なんですか?」
のように3パターンからランダムに選ばれる、というようなものです。差し込む文字列にも、差し込まれる文字列にも「固定した長さ」というものが存在しないわけですね。

こういうような場合には、『なんらかの目印をつけてから「2文字記法」で文字列の長さを自動補完する』というテクニックを使います。「2文字記法」とは「@.../.../...@」が持つ糖衣構文の1つで、 「指定した区切り文字までの文字列長を自動的に補完した上に、その区切り文字の削除までやってくれる」という便利なものです。

この「2文字記法」を使うことで、固定ではない文字列長に簡単に対応することができます。

まず、「差し込まれる」側のセリフテンプレートのテキストファイルを以下のように作成し、「phrases.txt」として「/images」に保存しておきます。


つぎに、「差し込む」側は[今日は$M月d日$, 電池は残り#P'%'#]を表示文字列に直に記述することにすると、テキスト全体の組み立てとしては、以下のようになります。
  1. 「差し込む」セリフ用の乱数を生成してスタックにプッシュ
  2. 「差し込まれる」テンプレート用の乱数を生成してスタックにプッシュ
  3. 「phrases.txt」からテンプレート文を取得
  4. 後で使う「@.../.../\S@」のために ",***,” を出力
  5. 2文字記法の目印として半角コロン ":” を出力
  6. 今日は$M月d日$, 電池は残り#P'%'#」を出力
  7. 半角コロン ":” を目印とする2文字記法で「@:m@」をおこなう
  8. @.../.../\S@」を使ってテンプレート中の「***」をセリフに置換
これをテキストに直すと、
  1. 2@p@rand@x@
  2. 3@p@rand@x@
  3. phrases@0/-1/\f@
  4. ,***,
  5. :
  6. 今日は$M月d日$, 電池は残り#P'%'#
  7. @:m@
  8. @0/-1/\S@
となります。


この「2文字記法」糖衣構文は、ver.4.4.1から追加されたものですが、かなり強力な記述力を備えた記法ですので、是非とも使い方を理解してください。


作例が思いつかなくなってきましたので、これで一旦終了

4 件のコメント:

  1. こんにちは。
    今時報マスコットを作っています。質問があるのですが、「一定の時間にのみランダムにテキストが表示されるように」するようにはどうしたらいいのでしょう?
    例えば、11時~12時までは「こんにちは」「お昼だね」「ご飯を食べよう」の三種類がランダムに、12時~2時までは「こんにちは」「まだ頑張ろう」「もうこんな時間だね」の三種類がランダムに表示されるようにしたいのです。
    上記を参考に$H$@p@6,11,13,17,20,23@0/-1/\g@@p@serifu01,serifu02,serifu03,serifu04,serifu05@0/-1/\f@としてみたり$H$@p@0@p@>=@x@$H$@p@5@p@<@x@and@p@rand@x@x@.0@P@_serihu01みたいなのを試してみましたが、全てだめでした。どうしたらいいでしょうか?

    返信削除
    返信
    1. そりゃまるでダメでしょうね。
      「@.../.../\f@」の使い方を理解されてませんよ。

      削除
  2. そうですか。説明を呼んで「f/F=ファイルの内容を置換する文字列」だと理解していたのでこう使ったのですが…。勉強が足りませんでした。
    「時刻別にテキストを表示する事」と「ランダムにテキストを表示する事」には成功したので、その二つを合体させたらできると思っていましたが、全く別の文字列が必要なのでしょうか?それとも二つを別々にして、トリガーとテキストなどで動かしたほうがいいでしょうか?
    ちなみに、時刻別の文字列は
    「$H$@p@6,11,13,17,20,23@0/-1/\g@@p@FILE@0/-1//\f@」ランダムテキストの文字列は「10@p@rand@x@FILE@0/10/\f@」です。

    返信削除
    返信
    1. > $H$@p@6,11,13,17,20,23@0/-1/\g@@p@serifu01,serifu02,serifu03,serifu04,serifu05@0/-1/\f@

      このシーケンス最後の「@0/-1/\f@」の該当部分文字列は何になっていますか?
      「@.../.../\f@」は「該当部分文字列をファイル名とするテキストファイル」から行を取り出すものです。

      もう一度よーくご確認してみられたほうが良いでしょう。

      削除