surfaces-mixer
YAMLからsurfaces.txtを生成するCLIアプリケーション
機能概要
伺かゴーストを作るとき、キャラクターの見た目を定義するsurfaces.txtを書く必要があります。シンプルなゴーストであれば手書きでも問題ありませんが、「目」「眉」「口」「腕」など複数のパーツを組み合わせて多彩な表情を作りたい場合、その作業量は膨大になります。
たとえば、目が5種類、眉が4種類、口が3種類あるとします。これらの全組み合わせは 5×4×3 = 60通りです。さらに腕のポーズが3種類あれば 60×3 = 180通りになります。これらすべてをsurfaces.txtに手で書くのは現実的ではありません。
surfaces-mixerは、この問題を解決するツールです。パーツの情報をYAMLファイルに書いておけば、全組み合わせを網羅したsurfaces.txtを自動で生成します。
特徴
全組み合わせを自動で生成します
YAMLファイルに各パーツ(目、眉、口など)のバリエーションを定義するだけで、それらの全組み合わせを計算し、surfaces.txtとして出力します。何百通りもの組み合わせがあっても、コマンドを1回実行するだけで完了します。手作業では何時間もかかる作業が一瞬で終わり、書き間違いの心配もありません。
サーフェス番号を自動で割り振ります
全組み合わせを作ると、それぞれにサーフェス番号を付ける必要があります。surfaces-mixerは、番号を見ただけでどのパーツの組み合わせか分かるような、規則的な番号を自動で割り振ります。たとえば「目が2番目、口が3番目、腕が1番目」の組み合わせなら「231」のような番号になります。これにより、スクリプトを書くときにサーフェス番号を把握しやすくなります。
パーツの追加・削除が簡単です
従来の方法では、パーツを1つ追加するだけでも、関連するすべてのサーフェス定義を書き直す必要がありました。surfaces-mixerなら、YAMLファイルにパーツを追加して再実行するだけです。削除も同様で、YAMLから消して再実行すれば、新しいsurfaces.txtが生成されます。影響範囲を気にする必要はありません。
ダウンロード
クイックスタート
# 基本的な使い方./surfaces-mixer -i input.yaml -o surfaces.txt# 上書き確認をスキップ(スクリプト向け)./surfaces-mixer -i input.yaml -o surfaces.txt -f
YAMLファイルの記述方法
実際に使用されているサンプルファイルはsample.yamlを、変換結果はsurfaces_sample.txtをご参照ください。
簡易なサンプル
2パーツグループ(目と腕)、各2バリエーションの最小構成例です。
raw: | descript { version,1 }characters: - base: | element0,overlay,body.png,0,0 parts: - group: 目 details: - name: 開き text: | element1,overlay,eye_open.png,0,0 - name: 閉じ text: | element1,overlay,eye_close.png,0,0 - group: 腕 details: - name: 下げ text: | element2,overlay,arm_down.png,0,0 - name: 上げ text: | element2,overlay,arm_up.png,0,0
この例では、サーフェス番号11〜22の4パターンが生成されます。
出力例:
charset,UTF-8descript{ version,1}// 目surface11,21{ // 開き element1,overlay,eye_open.png,0,0}surface12,22{ // 閉じ element1,overlay,eye_close.png,0,0}// 腕surface11,12{ // 下げ element2,overlay,arm_down.png,0,0}surface21,22{ // 上げ element2,overlay,arm_up.png,0,0}surface.append11-22{ element0,overlay,body.png,0,0}
複数キャラクターのサンプル
\0と\1の2キャラクターを定義する例です。各キャラクターで異なるパーツ構成が可能です。
characters: # \0側キャラクター - base: | element0,overlay,char0_body.png,0,0 parts: - group: 目 details: - name: 開き text: element1,overlay,char0_eye_open.png,0,0 - name: 閉じ text: element1,overlay,char0_eye_close.png,0,0 # \1側キャラクター(パーツ構成が異なってもよい) - base: | element0,overlay,char1_body.png,0,0 parts: - group: 表情 details: - name: 通常 text: element1,overlay,char1_normal.png,0,0 - name: 笑顔 text: element1,overlay,char1_smile.png,0,0 - name: 驚き text: element1,overlay,char1_surprise.png,0,0
この例では、\0は11〜12、\1は101〜103のサーフェス番号が生成されます(オフセット100が自動適用)。
フィールドリファレンス
トップレベル
| フィールド | 必須 | 説明 |
|---|---|---|
characters | Yes | キャラクター定義の配列。最低1つ必要 |
raw | No | パーツ組み合わせに関係しない定義。charset,UTF-8の直後に出力される |
Character(characters配列の各要素)
| フィールド | 必須 | 説明 |
|---|---|---|
parts | Yes | パーツグループの配列。最低1つ必要 |
base | No | 全サーフェスに共通する定義。surface.append形式で出力される |
GroupData(parts配列の各要素)
| フィールド | 必須 | 説明 |
|---|---|---|
group | Yes | パーツグループ名。出力時にコメントとして挿入される |
details | Yes | パーツバリエーションの配列。最低1つ必要 |
PoseData(details配列の各要素)
| フィールド | 必須 | 説明 |
|---|---|---|
name | Yes | パーツ名。出力時にコメントとして挿入される |
text | No | サーフェス定義本体。空でも空ブロックが生成される |
詳細仕様
サーフェス番号の割り当て
サーフェス番号は各パーツグループの選択を桁で表現します。各パーツグループには、そのポーズ数に応じた桁数が動的に割り当てられます。
- 1〜9種類のパーツ → 1桁
- 10〜99種類のパーツ → 2桁
- 100〜999種類のパーツ → 3桁
これにより、1パーツあたりのポーズ数に理論上の上限はありません。必要な桁数が自動的に確保されます。
例: 目(12種類)、口(3種類)、腕(4種類)の場合
- 目は2桁(01〜12)、口は1桁(1〜3)、腕は1桁(1〜4)
- サーフェス番号は
0111〜1234の範囲で生成されます
また、YAMLでのパーツグループの定義順序がサーフェス番号の桁配置に影響します。最初に定義したグループが最上位桁、最後に定義したグループが最下位桁となり、最下位桁が最も頻繁に変化します。
例: 顔色(2種)→目(2種)→腕(2種)の順で定義した場合
- 生成順序:
111→112→121→122→211→212→221→222 - 腕(最後に定義)の値が最も頻繁に変わり、顔色(最初に定義)の値が最も遅く変わります
サーフェス番号の読み解き方
サーフェス番号から各パーツの値を逆引きできます。番号を左から順に、各パーツグループの桁数分ずつ分割します。
手順:
- 各パーツグループのポーズ数から必要な桁数を確認(1-9種=1桁、10-99種=2桁...)
- サーフェス番号を左から桁数分ずつ分割
- 各値がそのパーツグループの何番目かを示す
例: 腕(4種)→口(3種)→目(12種)の構成でサーフェス番号2305の場合
- 腕は1桁 →
2= 2番目の腕パーツ - 口は1桁 →
3= 3番目の口パーツ - 目は2桁 →
05= 5番目の目パーツ
複数キャラクター定義時の動作
YAMLで複数のキャラクター(\0、\1など)を定義した場合、サーフェス番号が衝突しないよう自動的にオフセットが計算されます。オフセットは全キャラクターの最大サーフェス番号の桁数に基づいて決定されます。
例: \0の最大サーフェス番号が234(3桁)の場合
- オフセット = 10^3 = 1000
- \0のサーフェス番号: 111〜234
- \1のサーフェス番号: 1111〜1234(1000が加算される)
- \2のサーフェス番号: 2111〜2234(2000が加算される)
自動生成される要素
出力されるsurfaces.txtには、以下の要素が自動的に付加されます。
- charset宣言:
charset,UTF-8がファイル冒頭に自動追加されます(YAMLでの指定は不要) - コメント: YAMLの
groupとnameフィールドの値が、// グループ名や// パーツ名の形式でコメントとして挿入されます - surface.append定義: YAMLの
baseフィールドはsurface.append{最小番号}-{最大番号}形式に自動変換され、全サーフェスに共通する定義として出力されます - 空のtextフィールド:
textフィールドが空でも、コメント付きの空surfaceブロックが生成されます。パーツ組み合わせのプレースホルダーとして利用できます
rawフィールドの使い方
パーツの組み合わせに関係しない定義(descriptブロックや単独のsurface定義など)は、YAMLのrawフィールドに記述します。rawの内容はcharset,UTF-8宣言の直後に出力されます。
例:
raw: | descript { version,1 } surface0 { element0,overlay,surface0.png,0,0 }
ホワイトリスト機能
-wオプションで出力するサーフェス番号を限定できます。カンマ区切りで番号を指定し、指定した番号のみが出力されます。
- オプション未指定時は全サーフェスが出力されます
- 完全一致のみ対象: 部分一致はしません
- オフセット込みで指定: 複数キャラクター定義時、\1以降のサーフェスはオフセット込みの番号を指定します
例: \0のサーフェス111と\1のサーフェス1111のみを出力
./surfaces-mixer -i input.yaml -o output.txt -w "111,1111"
出力ファイルの構造
出力されるsurfaces.txtは以下の順序で構成されます。
charset,UTF-8宣言rawフィールドの内容(指定時のみ)- 各キャラクターのサーフェス定義
- 各パーツグループのsurface定義
surface.append定義(base指定時のみ)
同一の定義を持つサーフェス番号は、カンマ区切りで結合されて出力されます。
出力例:
charset,UTF-8surface111,121{ // 目開き element1,overlay,eye_open.png,0,0}surface.append111-122{ element0,overlay,base.png,0,0}
制約事項
- 文字コード: 入力YAMLファイルはUTF-8で記述してください。出力ファイルは常にUTF-8で生成されます
- 必須要素:
characters配列に最低1つのキャラクター、各partsに最低1つのグループ、各detailsに最低1つのパーツが必要です - 定義順序:
parts配列の定義順序がサーフェス番号の桁配置に影響します。変更すると既存のサーフェス番号が変わる可能性があります
コマンドラインオプション
| オプション | 必須 | 説明 |
|---|---|---|
-i | Yes | 入力YAMLファイルのパスを指定します。このオプションは必須です。 |
-o | No | 出力ファイルのパスを指定します。省略した場合、標準出力に結果が出力されます。 |
-f | No | 出力ファイルが既に存在する場合の上書き確認をスキップします。スクリプトでの自動実行時に便利です。 |
-w | No | 出力するサーフェス番号をカンマ区切りで指定します。指定した番号のみが出力されます。詳細は「ホワイトリスト機能」を参照してください。 |
-v | No | バージョン情報を表示して終了します。 |
-h | No | ヘルプメッセージを表示して終了します。 |
使用例
特定のサーフェスのみ出力:
./surfaces-mixer -i input.yaml -o surfaces.txt -w "111,112,121"
標準出力に出力(パイプやリダイレクトで利用):
./surfaces-mixer -i input.yaml > surfaces.txt
トラブルシューティング
| エラーメッセージ | 原因 | 対処法 |
|---|---|---|
error while parsing args | コマンドラインオプションの指定ミス | -iオプションが指定されているか確認 |
<path> is directory | 出力先にディレクトリが指定された | -oオプションでファイルパスを指定 |
error while loading yaml | YAMLファイルの読込または構文エラー | YAMLの構文が正しいか確認。ファイルが存在するか確認 |
error while generating surface offset | サーフェスオフセット計算失敗 | 各groupに最低1つのdetailsがあるか確認 |
error while writing surfaces.txt | 出力ファイルへの書き込み失敗 | 出力先ディレクトリの存在と書き込み権限を確認 |
FAQ
Q. 多数のサーフェスを用意すると動作は重くならないのか?
A. SSPにおいて、サーフェス定義の数が多くとも動作パフォーマンスにほとんど影響しません(Reference)。
とはいえ、作者の環境では現行のSSPでは21600通りのサーフェスを定義したsurfaces.txtを読み込もうとした際に8秒程度の遅延が確認されました。
組み合わせの数が増えすぎる場合は上記のホワイトリスト機能で削減することを検討してください。
YAML形式について
surfaces-mixerの入力ファイルはYAML形式で記述します。YAMLはJSONやXMLと同じくデータを記述するための形式ですが、人間が読み書きしやすいように設計されています。ここでは、surfaces-mixerを使うために必要な最低限の知識を説明します。
基本的な書き方
YAMLでは「キー: 値」の形式でデータを記述します。コロンの後には半角スペースが必要です。
name: 開きgroup: 目
インデント(字下げ)
YAMLではインデントを使ってデータの階層構造を表します。インデントには半角スペースを使い、タブ文字は使えません。同じ階層のデータは同じ深さのインデントで揃えます。
parts: - group: 目 details: - name: 開き - name: 閉じ
この例では、partsの中にgroupとdetailsがあり、detailsの中に2つのnameがあるという階層構造を表しています。
リスト(配列)
ハイフン(-)を使ってリストを表現します。同じ階層にある複数のハイフンは、同じリストの要素になります。
details: - name: 開き - name: 閉じ - name: 半目
複数行のテキスト
パイプ記号(|)を使うと、複数行のテキストをそのまま記述できます。パイプの次の行からテキストを書き始め、インデントを揃えます。
base: | element0,overlay,body.png,0,0 element1,overlay,head.png,0,0
この書き方では、改行がそのまま保持されます。surfaces-mixerではbase、text、rawフィールドでこの形式をよく使います。
コメント
#以降はコメントとして扱われ、処理時には無視されます。
characters: # メインキャラクター - base: | element0,overlay,body.png,0,0
よくある間違い
- タブ文字を使う: YAMLではインデントにタブ文字は使えません。半角スペースを使ってください
- コロンの後にスペースがない:
name:開きではなくname: 開きと書きます - インデントがずれている: 同じ階層の要素はインデントを揃える必要があります
参考リンク
より詳しいYAMLの仕様については、以下を参照してください。