kengo700のダイアリー

誰の役にも立たないと思う情報を発信するブログ

2Dアクションゲーム『Hollow Knight』の日本語化のための試行錯誤メモ

2Dアクションゲーム『Hollow Knight』の日本語化のために試行錯誤した内容をメモしておく記事です。 まだ日本語化はできていません。 4月16日、日本語化パッチが完成しました!

はじめに

先日から『Hollow Knight』という2Dアクションゲームの有志翻訳に挑戦しています。 どんなゲームかは下記ページなどをご覧ください。

現在は翻訳に取り組む前に日本語化の方法を模索している段階です。 具体的には、ビットマップのフォントデータを作成できれば日本語化できそうなので、フォントデータのフォーマットを解析している途中です。

とりあえず調べたことなどを今後見直すために記録しておきます。

3/4追記:新たに分かったことは別記事に書こうと思っていましたが、分かりにくく面倒くさくなりそうなので、この記事にどんどん追記していこうと思います。

4/5追記:下訳が完成し、校正作業に移行しました。

4/16追記:日本語化パッチが完成しました。皆さまお疲れさまでした。日本語化の方法は下記ページをご覧ください。

8/4追記:公式日本語版(BETA)が配信されました! 本有志翻訳プロジェクトは終了となります。

試行錯誤

ゲームデータの確認

Steamのゲームのデータは、デフォルトでは下記の場所にインストールされる。

  • C:\Program Files (x86)\Steam\steamapps\common

Steamクライアントでゲームのプロパティを開き、「ローカルファイルを閲覧…」をクリックすると、ゲームデータの場所をエクスプローラで簡単に開くことができる。

f:id:kengo700:20170304015228p:plain

ちなみにプロパティの「ゲームキャッシュの整合性を確認…」をクリックすると、改変したゲームデータがデフォルトに戻るので、日本語化したものを元に戻せる。

『Hollow Knight』の場合は下図のように「*.assets」というファイルがあるので「Unity」で作られたゲームだということが分かる。

f:id:kengo700:20170304015804p:plain

UnityEXについて

Unityで作成されたゲームのデータは「UnityEX」というソフトでアンパック(展開)することができる(ここら辺は以前『Diluvion』の日本語化方法を調べた時に分かったこと)。

UnityEX - Zone of Games Forum

ダウンロードサイトはロシア語なので、Chromeの翻訳機能を使って内容を確認してダウンロード。

ちなみに「UnityEX」を紹介しているサイトにはどこも「念のためウィルスチェックをしたほうが良い」と書いてあるので、念のためウィルスチェックしておく。

UnityEXはGUI形式でもコマンドラインでも操作できる。

GUI形式の場合の操作は下記の通り。

  • アンパック
    • 「Open archive Unity」で「*.assets」を開く
    • ファイルを右クリックし「Export selected」で特定のファイルをアンパック
    • 「Export all files」で全てのファイルをアンパック
  • リパック
    • 上記手法でファイルをアンパック
    • ファイルを修正または別のファイルで上書き
    • 「Import all files」でリパック

f:id:kengo700:20170304050106p:plain

コマンドラインの場合は下記の通り

  • アンパック(txt形式のみ)
    • UnityEX.exe export resources.assets -t txt
  • リパック(txt形式のみ)
    • UnityEX.exe import resources.assets -t txt

コマンドラインの操作なので、下記のようなバッチファイルを作成してダブルクリックでもOK。

f:id:kengo700:20170304021452p:plain

「resources.assets」だけでなく、フォルダ内の「*.assets」全てからアンパックしたい場合は下記のバッチファイル。

f:id:kengo700:20170304021545p:plain

ファイル形式を限定しない場合は「-t txt」を削除する。

f:id:kengo700:20170304021620p:plain

UnityEXでテキストデータをアンパック

とりあえずテキストデータが欲しいのでtxt形式のファイルだけアンパックしたところ、下記のファイルが取り出せた。

  • _Backer Messages.txt
  • _Elderbug.txt
  • _Quirrel.txt
  • DE_*.txt
  • EN_*.txt
  • ES_*.txt
  • FR_*.txt
  • LineBreaking Following Characters.txt
  • LineBreaking Leading Characters.txt
  • PlayMakerAssemblies.txt
  • Price_Prices.txt
  • Type_Prices.txt
  • Notes_*.txt
  • Word Count_*.txt

どう見ても「DE」「EN」「ES」「FR」から始まるファイルが怪しいので、TeraPadで「EN_MainMenu.txt」を開くとこんな感じ。

f:id:kengo700:20170304022307p:plain

というわけで全部で43個ある「EN_*.txt」にゲームのテキストがまとめられていて、これを取り出すことに成功。

テキストデータがtxt形式で、わかりやすいフォーマットで、種類ごとに別ファイルまとめられているという、Unity製のゲームの中ではかなり多言語対応を考慮して作られているゲームっぽい。

UnityEXで翻訳データをリパック

試しに「EN_MainMenu.txt」と「EN_UI.txt」の一部を翻訳し、リパックしてゲームを起動してみる。ファイルはTeraPadで編集し、「UTF-8N」形式で保存。

f:id:kengo700:20170304022903p:plain

f:id:kengo700:20170304022910p:plain

メインメニューについては下図の通り日本語化できた。

f:id:kengo700:20170304023531p:plain

しかし会話文やアイテムの説明は「□□□□…」と文字化けしてしまう。

f:id:kengo700:20170304023604p:plain

いろいろ調べたところ、文字化けしている部分は「ビットマップフォント」が使われているらしい

フォントの種類について

フォントには「ダイナミックフォント」と「ビットマップフォント」の2種類がある。画像でいう「ベクター形式」と「ラスター形式」の違い。 詳しくは下記など。

ダイナミックフォントのファイルはttf形式かotf形式のファイル。

ビットマップフォントのファイルはfnt&dds形式など。 ビットマップフォントの場合は、画像のファイルと、各文字の座標情報のファイルの2つがセットになっている。

Unityでは、ダイナミックフォントの場合は、日本語フォントが見つからない場合はOSのフォントを探し出して使用してくれるらしい。 なので『Hollow Knight』のメニュ部分は日本語フォントを入れなくても日本語にできたっぽい。

しかし『Hollow Knight』のファイルの中にfnt形式やdds形式のファイルは見当たらず、どれがフォントデータか分からない。 なので中文化MODを探してみる。

中文化MODのデータの確認

下記ページで『Hollow Knight』を検索すると、すでに中文化MODが作成されていた。

f:id:kengo700:20170304024750p:plain

このMODのページをChromeで開こうとすると警告がでるので注意。たぶん下手な部分をクリックすると危険。

f:id:kengo700:20170304024936p:plain

ダウンロードしたファイルを解凍すると当然の如くウィルスチェックに引っかかるので、念のため「VMware」の仮想PC上で作業する。

下記ページを参考に、解凍したフォルダに「hollow_knight.exe」をコピーし「Hollow.Knight.CHS.PATCH.V2.0-ALI213.exe」を実行。

f:id:kengo700:20170304025222p:plain

左下のチェックはマルウェアらしいのでチェックを外し、右上のボタンをクリック。

f:id:kengo700:20170304025325p:plain

すると4種類のファイルが生成される。

f:id:kengo700:20170304025515p:plain

「logo.ress」ファイルは得体が知れないので放置して、残りの「resources.assets」「sharedassets0.assets」「sharedassets1.assets」を仮想PCから現PCへコピー。念のためウィルスチェックをしてからゲームファイルに上書き。

ゲームを起動すると中文化されている。タイトルロゴを改変しているこだわり具合。

f:id:kengo700:20170304025846p:plain

会話文やアイテムの文字も問題なく中文化されている。すげぇ…

f:id:kengo700:20170304025932p:plain

アンパックしたデータの比較

中文化時に改変されたファイルのうち、「sharedassets1.assets」はタイトルロゴのデータらしい(これだけ元に戻したところ、タイトルロゴが元に戻った)。

なので中文化に関わっているのは「resources.assets」「sharedassets0.assets」の2つのファイル。

比較するために、前述の「export_all.bat」で全てのファイルをアンパック。途中で謎のエラーが出るものの、「OK」を押しまくって進める。

中文化版とオリジナル版の内容を「FdateCompare」というソフトで比較。

翻訳データである「EN_*.txt」を除くと、変更されていたデータは下記の通り。

  • resources\ARIAL SDF Atlas.tex
  • resources\Perpetua-SDF Atlas.tex
  • resources\perpetua_tmpro Atlas.tex
  • resources\resources_00001.-2
  • resources\resources_00001.-20
  • resources\resources_00001.-5
  • resources\resources_00002.-5
  • resources\resources_00003.-5
  • resources\resources_00004.-5
  • resources\Textures\Perpetua-SDF Atlas.tex.dds
  • resources\Textures\perpetua_tmpro Atlas.tex.dds
  • resources\Textures\trajan_bold_tmpro Atlas.tex.dds
  • resources\trajan_bold_tmpro Atlas.tex
  • sharedassets0\sharedassets0_00001.-1
  • sharedassets0\Textures\TrajanPro-Bold SDF Atlas.tex.dds
  • sharedassets0\TrajanPro-Bold SDF Atlas.tex

どうやら「Trajan」「Perpetua」「Arial」というフォントが使われているもよう。

ddsは画像データで、UnityEXがtexから変換したもの?

texは「テクスチャ」だと思うが、ググってもあまり情報が出てこない。 残りのファイルがビットマップフォントの座標データだと思われる。

試しに「TrajanPro-Bold SDF Atlas.tex.dds」を「Windows Texture Viewer」で表示してみる。

オリジナル版の「TrajanPro-Bold SDF Atlas.tex.dds」。

f:id:kengo700:20170304034326p:plain

中文化後の「TrajanPro-Bold SDF Atlas.tex.dds」。確かに中国語の文字が並んでいる。

f:id:kengo700:20170304034357p:plain

ビットマップフォントデータのフォーマット確認

そもそもビットマップフォントのフォーマットについてよく知らないので調べてみる。

下記ページによると「Bitmap font generator」というソフトを使うと自分で作れるらしい。

どうやら特定の文字のフォントだけ出力できるっぽい。 ゲームで使っている文字のデータだけフォントにすることで、容量を削減したりするため?

特定の文字をテキストデータから入力できるらしい。しかし右のチェックを入れると特定の文字セットを全部選択できるみたいなので、とりあえず平仮名のデータを出してみる。

f:id:kengo700:20170304034744p:plain

f:id:kengo700:20170304034950p:plain

「Export Options」は「Text」と「Binary」の2種類を試す。

f:id:kengo700:20170304035002p:plain

「Options」の「Save bitmap font as…」から適当な名前でビットマップフォントを作成。 フォントの画像データと座標データのファイルが生成された。

f:id:kengo700:20170304035240p:plain

f:id:kengo700:20170304035336p:plain

f:id:kengo700:20170304035356p:plain

バイナリ形式の座標データを「Stirling」で開いてみる。

するとこんな感じ。

f:id:kengo700:20170304035656p:plain

このフォーマットの詳細は下記ページに書かれている。

バイナリ形式では「info」「common」「pages」「chars」「kerning pairs」5つのブロックに分かれていて、それぞれにデータが書き込まれているらしい。 これを上のデータに当てはめると、こんな感じなはず。

f:id:kengo700:20170304041322p:plain

各ブロックの先頭のバイトがブロックの種類(01~05)、その次の4バイトがブロックの長さを表す。

バイトのデータは下の桁から書かれているらしい。なので「44 07 00 00」は「00000744」。これを10進数に直すと1860になる。

作成したフォントはひらがな93個。1文字に20バイトらしいので、93x20=1860。

文字部分を詳しく見てみるとこんな感じ。

f:id:kengo700:20170304042902p:plain

なのでバイナリ形式で「42 30 00 00 00 00 2B 00 12 00 13 00 02 00 08 00 16 00 00 0F」は、テキスト形式では「char id=12354 x=0 y=43 width=18 height=19 xoffset=2 yoffset=8 xadvance=22 page=0 chnl=15」

「char id」は識別番号のこと。「char id=12354」は「あ」を表す。

フォントデータの解析1

ビットマップフォントの座標データっぽい「sharedassets0_00001.-1」を見てみる。 左がオリジナル版、右が中文化版。

f:id:kengo700:20170304043826p:plain

どちらも「TrafanPro-Bold」という文字があり、「TrajanPro-Bold SDF Atlas」用のデータっぽい。

比較すると00005Eの位置までは一致している。ここまではフォントに関係ないデータ?

これがフォントの座標データだとすると、前節で見たように各文字のデータが繰り返されているはず。

「設定」の「拡張子別設定」から「00」の色を変えてみてたりして眺めると、マークを付けた部分が連番になっていることに気付く。

f:id:kengo700:20170304044418p:plain

「拡張子別設定」から一行を36バイトにして表示してみると、とてもそれっぽい。上がオリジナル版、下が中文化版。

f:id:kengo700:20170304044638p:plain

「0x0020」「0x0021」「0x0022」はそれぞれ「 」「!」「"」を表している。「Basic Latin」の記号部分。

この座標データは前節の「Bitmap font generator」で作成したものとは違っている。フォーマット情報を探しているものの見つかっていない。 もしゲームオリジナルのフォーマットの場合は、地道に各バイトの意味を解析しないといかん。

「Bitmap font generator」のフォーマットに照らし合わせて考えると、id、x、y、width、height、xoffset、yoffset、xadvanceにそれぞれ4バイト、page、chnlにそれぞれ2バイトで合計36バイトではないかと思われる(そうすると値が大きくなりすぎてしまうのが気になるが…)。 ただし最後の4バイトについてはオリジナル版でも中文化版でも「00 00 80 3F」なので、ここは考える必要は無い?

となると、あと分からないのは00005E~0000B3の部分。

フォントデータの解析2

まずは0000B0~0000B3の部分。オリジナル版では「D2 00 00 00」(10進数では210)、中文化版では「3F 09 00 00」(10進数では2367)。 オリジナル版の文字部分は0000B4~001E3Cまでなので180~7740バイトの部分。1文字に36バイト使っているので、(7740-180)/36=210。 中文化版の文字部分は0000B4~014D90までなので180~85392バイトの部分。1文字に36バイト使っているので、(85392-180)/36=2367。 これらのことから、0000B0~0000B3の部分は文字数を表しているのではないかと考えられる。

000064~000065の部分も0000B0~0000B3と同じ値になっている(他のファイルでも確認)。なのでここも文字数を表している? 文字数の情報が2回出てくるのはよく分からんけど、とりあえず先に進める。

次に、00009C~00009Fと0000A0~0000A3の部分。ここは連続して同じ値になっている。オリジナル版では「00 00 00 44」(10進数では1140850688)、中文化版では「00 00 80 45」(10進数では1166016512)。各フォントのddsファイルをWindows Texture Viewerで見ると縦横のサイズが同じで正方形の画像になっている。他に連続して同じ値なっている部分が見当たらないので、00009C~00009Fと0000A0~0000A3の部分は画像の縦横のサイズを表していると思われる。しかし10進数にするとサイズがおかしい。

そこでfloat型だと考えて変換すると、「00 00 00 44」は「512」、「00 00 80 45」は「4096」になる。オリジナル版の画像は512x512、中文化版では4096x4096なので、00009C~00009Fと0000A0~0000A3の部分は画像のサイズを表していると考えられる。

f:id:kengo700:20170304191651p:plain

f:id:kengo700:20170304191715p:plain

00005E~0000B3の部分のうち、分かった部分をまとめると下記の通り。オリジナル版と中文化版で変わっていない部分は灰色にしている。あと分からないのは5箇所。

f:id:kengo700:20170304192220p:plain

Bitmap Font Generatorのフォーマットを参考にすると、他にありそうなデータは「size」「lineHeight」「base」くらいか。

sizeはフォントの大きさ、lineHeightは複数行に渡るときの行の高さ?、Baseは行の上端から文字のbaseまでの距離?。

フォントデータの解析3

次は00005C~00005Fの部分。値は下記の通り。

  • 中文化版(全ファイル共通)
    • 00 00 F0 41(float、30)
  • オリジナル版(sharedassets0_00001.-1、TrajanPro-Bold SDF)
    • 00 00 0C 42(float、35)
  • オリジナル版(resources_00001.-5、Perpetua-SDF)
    • 00 00 40 42(float、48)
  • オリジナル版(resources_00002.-5、perpetua_tmpro)
    • 00 00 A6 42(float、83)
  • オリジナル版(resources_00003.-5、trajan_bold_tmpro)
    • 00 00 78 42(float、62)
  • オリジナル版(resources_00004.-5、Arial)
    • 00 00 AA 42(float、85)

これは「size」っぽい?

他の4箇所の値もまとめてチェックする。数では左側がオリジナル版、右側が中文化版。

f:id:kengo700:20170305150913p:plain

sharedassets0_00001.-1の00005C~0000B3の部分には分からない数値が5箇所ある。この00005Cを基準の1バイト目とすると、分からない部分は1~4、13~16、21~24、29~32、37~40バイト目の部分(他にも意味が分かっていない部分があるが、オリジナル版と中文化版で値が変わっていないので、とりあえず無視して進める)。

一覧にすると下記の通り。21~24と37~40が一致していることぐらいしか分からない。

1~4 13~16 21~24 29~32 37~40
中文化版 00 00 F0 41 00 00 F0 41 00 00 F0 41 00 00 00 00 00 00 F0 41
オリ版、1.-1 00 00 0C 42 00 00 28 42 00 00 D2 41 00 00 0C C1 00 00 D2 41
オリ版、1.-5 00 00 40 42 00 00 5C 42 00 80 1D 42 00 00 7A C1 00 80 1D 42
オリ版、2.-5 00 00 A6 42 00 40 BE 42 00 20 88 42 00 80 D8 C1 00 20 88 42
オリ版、3.-5 00 00 78 42 00 C0 94 42 00 00 3A 42 00 00 78 C1 00 00 3A 42
オリ版、4.-5 00 00 AA 42 00 80 C3 42 00 E0 99 42 00 00 90 C1 00 E0 99 42

これをfloat型で変換すると下記の通り。

1~4 13~16 21~24 29~32 37~40
中文化版 30 30 30 0 30
オリ版、1.-1 35 42 26.25 -8.75 26.25
オリ版、1.-5 48 55 39.375 -15.625 39.375
オリ版、2.-5 83 95.125 68.0625 -27.0625 68.063
オリ版、3.-5 62 74.375 46.5 -15.5 46.5
オリ版、4.-5 85 97.75 76.9375 -18 76.938

これは合ってるかどうか微妙な値だ… 1~4がsize、13~16がlineHeight、21~24と37~40がbaseではないかと思われるが、よく分からない。まあ中文化版ではシンプルな値になっているので、これを参考に作成して、問題が出たら修正すればいいようにも思う。

フォントデータの解析4

上記のフォントデータを改変してゲームにどう反映されるかを確認してみる。

改変前のスクショは下記。

f:id:kengo700:20170305154526p:plain

まずはresources_00002.-5の000054~000057(sizeだと思われる部分)を00 00 A6 42(float、83)から00 00 20 42(float、40)に変更してリパックしてゲームを起動してみる。

f:id:kengo700:20170305154540p:plain

文字が大きくなった。83から40に小さくしたことで文字が逆に大きくなる理屈は考えるのがめんどいのでスルーして、とりあえずこの部分がsizeっぽいのは分かった。

次に上記の変更は元に戻して、resources_00002.-5の000060~000063(lineHeightだと思われる部分)を00 40 BE 42(float、95.125)から00 00 16 43(float、150)に変更してリパックしてゲームを起動してみる。

f:id:kengo700:20170305155226p:plain

行間が広まったので、やっぱりこの部分がlineHeightっぽい。

次に上記の変更は元に戻して、resources_00002.-5の000068~00006Bと000078~00007B(baseだと思われる部分)を00 20 88 42(float、68.0625)から00 00 F0 42(float、120)に変更してリパックしてゲームを起動してみる(とりあえず二箇所両方変える)。

f:id:kengo700:20170305160652p:plain

違いが分かりにくいが全体が下に移動している。やっぱりこの部分がbaseっぽい。二箇所の違いはとりあえず後回しで。

次に上記の変更は元に戻して、resources_00002.-5の000070~000073(謎の部分)を00 80 D8 C1(float、-27.0625)から00 00 F0 C2(float、-120)に変更してリパックしてゲームを起動してみる

f:id:kengo700:20170305161234p:plain

変化は見られない。とりあえずここはスルーして先に進める。

フォントデータの解析5

文字の座標部分について確認してみる。

「perpetua_tmpro Atlas.tex.dds」(UnityEXでtexをddsに変換したもの)をWindows Texture Viewerで開き、形が分かりやすい「I」の左上と右下のだいたいの座標をチェックすると下図の通り。

f:id:kengo700:20170305175532p:plain

このフォントの座標データを「resources_00002.-5」から探すと、恐らく下図の部分。

f:id:kengo700:20170305175727p:plain

この数値を抜き出し、float型で変換すると下記の通り。

  • 元データ:49 00 00 00, 00 00 C1 43, 00 00 28 42, 00 80 88 41, 00 40 3E 42, 00 00 10 40, 00 40 3E 42, 00 80 AC 41, 00 00, 80 3F
  • float型:73, 388, 42, 17.0625, 47.5625, 2.25, 47.5625, 21.5625, 0, 1

73は識別記号で「LATIN CAPITAL LETTER I」を表す。

それ以降の数字は、id、x、y、width、height、xoffset、yoffset、xadvance、page、chnlと考えるとそれっぽい。まあ正直xoffsetとかxadvanceとかの意味はよく分かってないので、とりあえずそうだとしてフォントファイルを作成してみて、問題があればまた考えよう。

日本語フォントの作成テスト

とりあえず「あ」と「い」の2文字だけのフォントを作ってみる。

f:id:kengo700:20170305192738p:plain

f:id:kengo700:20170305192808p:plain

出力オプションのBit depthを8にしたら表示されなかったので32にしている。

f:id:kengo700:20170305192833p:plain

出力すると2つのファイルが生成される。

f:id:kengo700:20170305192917p:plain

ddsの方は「perpetua_tmpro Atlas.tex.dds」にリネームしておく。fntの方を開くと下図の通り。

f:id:kengo700:20170305193049p:plain

この情報を元に手動で座標データファイルを作成する。「resources_00002.-5」を開き、前節までの解析結果をもとに下図のように編集してみた。

f:id:kengo700:20170305193338p:plain

そして「EN_Elderbug.txt」のセリフを下記のように編集。

f:id:kengo700:20170305193435p:plain

用意したファイルを下記のようにフォルダに入れる。

  • resources.assets(オリジナルのファイル)
  • sharedassets0.assets(オリジナルのファイル)
  • UnityEX.exe
  • Unity_Assets_Files
    • resources
      • resources_00001.-2(中文化版から抽出したファイル)
      • resources_00001.-5(中文化版から抽出したファイル)
      • resources_00002.-5(上記の手動で作成したやつ)
      • resources_00003.-5(中文化版から抽出したファイル)
      • resources_00004.-5(中文化版から抽出したファイル)
      • resources_00001.-20(中文化版から抽出したファイル)
      • EN_*.txt(オリジナルのファイル)
      • EN_Elderbug.txt(上記のセリフを一部変えたやつ)
      • Textures
        • perpetua_tmpro Atlas.tex.dds(上記の作成したやつ)
        • Perpetua-SDF Atlas.tex.dds(中文化版から抽出したファイル)
        • trajan_bold_tmpro Atlas.tex.dds(中文化版から抽出したファイル)
        • res_logo
          • ARIAL SDF Atlas.tex.dds(中文化版から抽出したファイル)
    • sharedassets0
      • sharedassets0_00001.-1
      • Textures
        • TrajanPro-Bold SDF Atlas.tex.dds(中文化版から抽出したファイル)

UnityEXを起動し、「resources.assets」と「sharedassets0.assets」をそれぞれ開いて「Import all files」を実行。 リパックされた「resources.assets」と「sharedassets0.assets」をゲームのフォルダにコピー。ゲームを起動。

f:id:kengo700:20170305194320p:plain

なんかずれとる…

文字を変えてみると、いろいろと設定があっていないっぽい。「Padding」や「Spacing」とかの部分かな?

f:id:kengo700:20170305195325p:plain

フォントデータの解析6

今のところ分かっている情報は下記の通り。

f:id:kengo700:20170305201449p:plain

この灰色に塗ってある、オリジナル版と中文化版で変化が無いので無視していた部分が、作成した日本語フォントの設定と合っていない可能性がある。

一部抜き出すと、下記の通り。

  • 00 00 80 3F, ~, 00 00 D7 41, ~
  • 00 00 60 C0, 00 00 00 3F, 00 00 60 C0, 00 00 E0 3F, 00 00 D2 42, 00 00 A0 40

float型に直すと、下記の通り。

  • 1, 26.875
  • -3.5, 0.5, -3.5, 1.75, 105, 5

…よく分からん。

日本語フォントの作成テスト2

「Padding」や「Spacing」はよく分からなかったので、等間隔のフォント画像を作ることにする。 「Equialize the cell heights」にチェック。加えてSpacgingを10にして文字を離してみる。

f:id:kengo700:20170305202940p:plain

f:id:kengo700:20170305203040p:plain

生成したfntファイルは下記。

f:id:kengo700:20170305203115p:plain

これを元に座標データを修正。

f:id:kengo700:20170305203210p:plain

リパックしてゲームを起動。

f:id:kengo700:20170305202837p:plain

ちゃんと表示された。

英語部分とずれている理由は不明。そこらへんはlineHeightやbaseの値をいじれば調整できるかな?

そもそも「あ」と「い」だけのフォントなのにアルファベットも表示されているのは謎。あるビットマップフォントで見つからない文字は、他のビットマップフォントから持ってくるような機能がUnityにあるのかもしれない。

フォントの座標データ変換プログラムの作成

前節まで手動で行っていた座標データの修正を自動的に行う。

とりあえずバイナリ関連のプログラミングを調べる。

float型の数字を出力してみる。

using System;
using System.IO;

namespace FontConverterForHollowKnight
{
    class Program
    {
        static void Main(string[] args)
        {

            float x = 25;
            byte[] data = BitConverter.GetBytes(x);
            File.WriteAllBytes("testnew.bin", data);
            
        }
    }
}

f:id:kengo700:20170305214938p:plain

複数の情報を出力する。

            float x = 10;
            float y = 11;
            float width = 12;
            float height = 13;

            FileStream fs = null;

            // ファイルを新規作成(既に存在する場合は上書き)
            fs = new FileStream(@"testnew.bin", FileMode.Create);

            // バイナリデータの書き込み
            byte[] data_x = BitConverter.GetBytes(x);
            fs.Write(data_x, 0, data_x.Length);

            byte[] data_y = BitConverter.GetBytes(y);
            fs.Write(data_y, 0, data_y.Length);

            // バイナリデータの書き込み
            byte[] data_width = BitConverter.GetBytes(width);
            fs.Write(data_width, 0, data_width.Length);

            byte[] data_height = BitConverter.GetBytes(height);
            fs.Write(data_height, 0, data_height.Length);

            fs.Close();

f:id:kengo700:20170305221507p:plain

XML形式のファイルの読み込みを復習。

検証のためにひらがなだけのビットマップフォントを作成した。fntファイルの内容は下記の通り。

f:id:kengo700:20170305224929p:plain

とりあえず座標データ部分だけを読み込んでみる。

            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load("hiragana.fnt");
            
            XmlNodeList nodeList = xmlDocument.SelectNodes(@"/font/chars");

            foreach (XmlNode node in nodeList)
            {

                XmlElement element = (XmlElement)node;
                XmlAttribute attr_count = element.GetAttributeNode("count");
                Console.WriteLine(attr_count.InnerText);

                for (int i = 0; i < node.ChildNodes.Count; i++)
                {
                    XmlElement element_char = (XmlElement)node.ChildNodes[i];

                    XmlAttribute attr_id = element_char.GetAttributeNode("id");
                    XmlAttribute attr_x = element_char.GetAttributeNode("x");
                    XmlAttribute attr_y = element_char.GetAttributeNode("y");
                    XmlAttribute attr_width = element_char.GetAttributeNode("width");
                    XmlAttribute attr_height = element_char.GetAttributeNode("height");
                    XmlAttribute attr_xoffset = element_char.GetAttributeNode("xoffset");
                    XmlAttribute attr_yoffset = element_char.GetAttributeNode("yoffset");
                    XmlAttribute attr_xadvance = element_char.GetAttributeNode("xadvance");

                    Console.WriteLine("{0},{1},{2},{3},{4},{5},{6},{7}",
                        attr_id.InnerText, attr_x.InnerText, attr_y.InnerText, attr_width.InnerText, attr_height.InnerText,
                        attr_xoffset.InnerText, attr_yoffset.InnerText, attr_xadvance.InnerText);

                }       

            }

ちゃんと読み込めた。

f:id:kengo700:20170305225109p:plain

fntファイルの座標データ部分を読み込んで、バイナリ形式で保存する。

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;

namespace FontConverterForHollowKnight
{
    class Program
    {
        static void Main(string[] args)
        {

            int count = 0;

            List<int> id = new List<int>();
            List<float> x = new List<float>();
            List<float> y = new List<float>();
            List<float> width = new List<float>();
            List<float> height = new List<float>();
            List<float> xoffset = new List<float>();
            List<float> yoffset = new List<float>();
            List<float> xadvance = new List<float>();

            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load("hiragana.fnt");
            
            XmlNodeList nodeList = xmlDocument.SelectNodes(@"/font/chars");

            foreach (XmlNode node in nodeList)
            {

                XmlElement element = (XmlElement)node;
                XmlAttribute attr_count = element.GetAttributeNode("count");
                Console.WriteLine(attr_count.InnerText);

                count = int.Parse(attr_count.InnerText);

                for (int i = 0; i < node.ChildNodes.Count; i++)
                {
                    XmlElement element_char = (XmlElement)node.ChildNodes[i];

                    XmlAttribute attr_id = element_char.GetAttributeNode("id");
                    XmlAttribute attr_x = element_char.GetAttributeNode("x");
                    XmlAttribute attr_y = element_char.GetAttributeNode("y");
                    XmlAttribute attr_width = element_char.GetAttributeNode("width");
                    XmlAttribute attr_height = element_char.GetAttributeNode("height");
                    XmlAttribute attr_xoffset = element_char.GetAttributeNode("xoffset");
                    XmlAttribute attr_yoffset = element_char.GetAttributeNode("yoffset");
                    XmlAttribute attr_xadvance = element_char.GetAttributeNode("xadvance");

                    Console.WriteLine("{0},{1},{2},{3},{4},{5},{6},{7}",
                        attr_id.InnerText, attr_x.InnerText, attr_y.InnerText, attr_width.InnerText, attr_height.InnerText,
                        attr_xoffset.InnerText, attr_yoffset.InnerText, attr_xadvance.InnerText);

                    id.Add(int.Parse(attr_id.InnerText));
                    x.Add(float.Parse(attr_x.InnerText));
                    y.Add(float.Parse(attr_y.InnerText));
                    width.Add(float.Parse(attr_width.InnerText));
                    height.Add(float.Parse(attr_height.InnerText));
                    xoffset.Add(float.Parse(attr_xoffset.InnerText));
                    yoffset.Add(float.Parse(attr_yoffset.InnerText));
                    xadvance.Add(float.Parse(attr_xadvance.InnerText));

                }       

            }

            float pagechnl = 1;

            FileStream fs = null;

            // ファイルを新規作成(既に存在する場合は上書き)
            fs = new FileStream(@"testnew.bin", FileMode.Create);

            byte[] data_count = BitConverter.GetBytes(count);
            fs.Write(data_count, 0, data_count.Length);

            for(int i=0; i< count; i++)
            {
                Console.WriteLine("{0}", i);

                byte[] data_id = BitConverter.GetBytes(id[i]);
                fs.Write(data_id, 0, data_id.Length);

                byte[] data_x = BitConverter.GetBytes(x[i]);
                fs.Write(data_x, 0, data_x.Length);

                byte[] data_y = BitConverter.GetBytes(y[i]);
                fs.Write(data_y, 0, data_y.Length);

                byte[] data_width = BitConverter.GetBytes(width[i]);
                fs.Write(data_width, 0, data_width.Length);

                byte[] data_height = BitConverter.GetBytes(height[i]);
                fs.Write(data_height, 0, data_height.Length);

                byte[] data_xoffset = BitConverter.GetBytes(xoffset[i]);
                fs.Write(data_xoffset, 0, data_xoffset.Length);

                byte[] data_yoffset = BitConverter.GetBytes(yoffset[i]);
                fs.Write(data_yoffset, 0, data_yoffset.Length);

                byte[] data_xadvance = BitConverter.GetBytes(xadvance[i]);
                fs.Write(data_xadvance, 0, data_xadvance.Length);

                byte[] data_pagechnl = BitConverter.GetBytes(pagechnl);
                fs.Write(data_pagechnl, 0, data_pagechnl.Length);

            }

            fs.Close();

        }
    }
    
}

できてるっぽい

f:id:kengo700:20170305231623p:plain

ひらがな、カタカナ、アルファベットのビットマップフォントを作ってみる。

f:id:kengo700:20170305234725p:plain

作成したプログラムで座標データをバイナリ形式で作成。「resources_00002.-5」にコピペ。画像サイズとかの情報部分は手動で書き換え。

f:id:kengo700:20170305234425p:plain

リパックしてゲームを起動。

f:id:kengo700:20170305234545p:plain

縦方向のずれはあるものの、何とかなりそう。細かい修正・調整は後からすることにして、そろそろ翻訳にも取り掛かろう。

テキストデータの抽出プログラムの作成

3/6~

フォントデータを扱うのに疲れたので、テキストデータの処理をしてみる。

テキストデータが複数の「EN_*.txt」に分散しているので、日本語化作業所(スプレッドシート)を立てるために情報を抽出してcsvまたはtsvファイルにまとめるプログラムを作成する。

まずは一つのファイルの内容を読み込んで表示してみる。

using System;
using System.Collections.Generic;
using System.Xml;

namespace ExtractTextForHollowKnight
{
    class Program
    {
        static void Main(string[] args)
        {


            string inputFile = @"C:\Temp\hollowknight\EN_MainMenu.txt";

            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(inputFile);

            XmlNodeList nodeList = xmlDocument.SelectNodes(@"/entries/entry");

            foreach (XmlNode node in nodeList)
            {
                
                XmlElement element = (XmlElement)node;
                XmlAttribute attr_count = element.GetAttributeNode("name");

                Console.WriteLine(attr_count.InnerText);
                Console.WriteLine(element.InnerText);

            }

        }
    }
}

「EN_MainMenu.txt」の内容は下記の通りにXML形式っぽいので、フォント座標の変換プログラムを改変して流用。

f:id:kengo700:20170306190756p:plain

プログラムを実行すると下記の通り。ちゃんと読み込めている。

f:id:kengo700:20170306190905p:plain

読み込んだデータをtsv形式で保存する。tsv形式はデータをタブで区切った形式で、データをカンマで区切るcsv形式より少しだけ扱いやすいと思う(テキスト中のカンマの扱いが)。まあ大差ないので好きな方で大丈夫。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;

namespace ExtractTextForHollowKnight
{
    class Program
    {
        static void Main(string[] args)
        {

            string inputFile = @"C:\Temp\hollowknight\EN_MainMenu.txt";
            string outputFile = @"C:\Temp\hollowknight\hollowknight_en.tsv";

            // 読み込みデータの保存用変数
            List<string> input_id = new List<string>();
            List<string> input_text = new List<string>();

            // データファイルを開く
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(inputFile);

            XmlNodeList nodeList = xmlDocument.SelectNodes(@"/entries/entry");

            foreach (XmlNode node in nodeList)
            {
                
                XmlElement element = (XmlElement)node;
                XmlAttribute attr_count = element.GetAttributeNode("name");

                Console.WriteLine(Path.GetFileName(inputFile));
                Console.WriteLine(attr_count.InnerText);
                Console.WriteLine(element.InnerText);

                input_id.Add(attr_count.InnerText);
                input_text.Add(element.InnerText);

            }


            // 出力用ファイルを開く               
            using (StreamWriter writer = new StreamWriter(outputFile, false, Encoding.UTF8))
            {

                // 見出し行を出力
                writer.WriteLine("File\tID\tText");

                // データを書き込み
                for(int i=0; i<input_id.Count(); i++)
                {
                    writer.WriteLine("{0}\t{1}\t{2}", Path.GetFileName(inputFile), input_id[i], input_text[i]);
                }

            }

        }
    }
}

出力されたファイルは下記の通り。

f:id:kengo700:20170306192230p:plain

あとはフォルダ内のテキストファイルに同じ処理をするだけ。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;

namespace ExtractTextForHollowKnight
{
    class Program
    {
        static void Main(string[] args)
        {

            string inputFolder = @"C:\Temp\hollowknight";
            string outputFile = @"C:\Temp\hollowknight\hollowknight_en.tsv";

            List<string> inputFiles = new List<string>();

            // ファイル名の取得(フルパス)
            inputFiles.AddRange(Directory.EnumerateFiles(inputFolder, "*.txt", SearchOption.AllDirectories).ToList());


            // 出力用ファイルを開く               
            using (StreamWriter writer = new StreamWriter(outputFile, false, Encoding.UTF8))
            {
                // 見出し行を出力
                writer.WriteLine("File\tID\tText");


                // データファイルを一つずつ処理
                foreach (string inputFile in inputFiles)
                {
                    // 読み込みデータの保存用変数
                    List<string> input_id = new List<string>();
                    List<string> input_text = new List<string>();

                    // データファイルを開く
                    XmlDocument xmlDocument = new XmlDocument();
                    xmlDocument.Load(inputFile);

                    XmlNodeList nodeList = xmlDocument.SelectNodes(@"/entries/entry");

                    foreach (XmlNode node in nodeList)
                    {

                        XmlElement element = (XmlElement)node;
                        XmlAttribute attr_count = element.GetAttributeNode("name");

                        Console.WriteLine("{0}, {1}, {2}", Path.GetFileName(inputFile), attr_count.InnerText, element.InnerText);

                        input_id.Add(attr_count.InnerText);
                        input_text.Add(element.InnerText);

                    }

                    // データを書き込み
                    for (int i = 0; i < input_id.Count(); i++)
                    {
                        writer.WriteLine("{0}\t{1}\t{2}", Path.GetFileName(inputFile), input_id[i], input_text[i]);
                    }

                }

            }


        }
    }
}

f:id:kengo700:20170306193311p:plain

大丈夫っぽい。ほとんど昔作ったプログラムの流用で行けたので楽だった。

日本語化作業所を立てる

tsv形式のファイルができたので、もう作業所を作ってしまおう。tsv形式のファイルから「EN_*.txt」を書き換えるプログラムはどうとでもなるだろう。きっと…

Googleアカウントにログインした状態でスプレッドシートのページを開き、右下のボタンから新しいシートを作成。

f:id:kengo700:20170306193723p:plain

「ファイル」->「開く」->「アップロード」に作成したtsvファイルをドラッグ

f:id:kengo700:20170306193620p:plain

問題なく読み込めた。

f:id:kengo700:20170306193840p:plain

ざっと見てみると、一部のデータがちゃんとtsvファイルに読み込めてないように見える(IDの部分にテキストが入ってしまっている?)。

f:id:kengo700:20170306194401p:plain

しかし元のテキストファイルを見ると、元からそうなっている。中文化版もそのままになっているので、ここは訳さなくてもいいのかもしれない。日本語化フォントが完成したら試してみよう。

f:id:kengo700:20170306194537p:plain

f:id:kengo700:20170306194711p:plain

分かりやすいようにスプレッドシートの体裁を整える。

  • 幅をいい感じに
  • 一行目の見出し行を日本語に。色を付ける。
  • テキストの折り返し設定
    • 原文列は「折り返し」ID列は「切り詰め」
  • 進捗列を追加
    • 「データ」->「データの入力規則」で「リストを直接指定」。「✕,△,〇」で「保存」。他の行にもコピペ。
  • 未訳部分を色付け
    • 「表示形式」->「条件付き書式」で「カスタム数式」。「=($E2=“✕”)」と「=($E2=“△”)」をそれぞれ黄色と緑色にする。
  • 見出し行を固定
    • 「表示」->「固定」->「1行」
  • ファイル名、ID、原文を保護
    • 選択肢て右クリックの「範囲を保護」
    • ※誤って編集してしまうの防ぐために保護設定をしてみているが、保護すると幅を変更できなくなるので、保護しなくてもいいかもしれない。
  • シートの名前を変更
    • 左下のシートのタブを右クリックして「名前を変更」
  • 進捗情報を自動計算して表示
    • 原文の見出し行を下記に
=CONCATENATE("訳文(全体:",countif(E2:E,"?*"),"、翻訳済:",(countif(E2:E,"〇")+countif(E2:E,"△")),"、進捗:",fixed((countif(E2:E,"〇")+countif(E2:E,"△"))/countif(E2:E,"?*")*100,2),"%)")」

f:id:kengo700:20170306201033p:plain

シートを追加し、翻訳方法などの説明情報を書き込む。

f:id:kengo700:20170306202517p:plain

こんな感じでいいかな。とりあえず日本語化の方法が確立するまでは「作業所(仮)」ということにしておこう。

注意事項とかをまとめるページをどうするかは考え中。Wikiを立ち上げると管理がめんどいし、Steamのガイドかブログの記事でいいか。

念のために自動バックアップだけ設定しておこう。まずはGoogleドライブに適当なフォルダを作る。作業所のシートもそこに移動しておく。

f:id:kengo700:20170306202941p:plain

「ツール」->「スクリプトエディタ」に下記のスクリプトを書き込む。英数字の部分はシートとバックアップ用のフォルダを開いたときの英数字部分。

//ファイルのバックアップ
function backup() {  
  var file = DriveApp.getFileById('「シートのURLのランダムな英数字」');//バックアップしたいファイル
  var folder = DriveApp.getFolderById('「フォルダのURLのランダムな英数字」');  // ここで指定したフォルダにファイルが入る
  file.makeCopy(file.getName()+'-'+Utilities.formatDate(new Date(), 'JST', 'yyyy-MM-dd-HH'),folder);   
}

▶ボタンで実行し、承認の確認が出たら許可する。ちゃんとバックアップが作成されたことを確認。

f:id:kengo700:20170306203723p:plain

時計マークのボタンでトリガーの設定を開き、毎日実行されるように設定して保存する。

f:id:kengo700:20170306203817p:plain

最後に作業所をWebに公開する。「ファイル」->「共有」の右下の「詳細設定」。

f:id:kengo700:20170306204003p:plain

非公開となっている部分右の「変更」をクリックし、「オン - リンクを知っている全員」にチェックを入れ、アクセスを「編集者」にして「保存」。

f:id:kengo700:20170307135043p:plain

※「オン - ウェブ上で一般公開」でもいいと思うが、日本語化についての注意点をまとめたページを見てから作業所を開いてほしいので「オン - リンクを知っている全員」にしてみている。どっちでもいいと思う。

あとは「共有するリンク」のURLをどこかに公開する。

注意点をまとめたページはそのうち作りますが、上記作業所で翻訳作業を開始していただいてもOKです。

ただしこの記事に書いてある通り、まだフォントの作成も完了しておらず、日本語化の手法が確立していない状態ということをご了承ください。 今後何かしらの問題が発生して日本語化パッチの作成が不可能となった場合、翻訳作業も無駄になってしまうかもしれません。

翻訳データの挿入プログラムの作成

3/7~

日本語化作業所(スプレッドシート)から出力した翻訳データ(csvまたはtsv形式)を元に、「EN*.txt」を書き換えるプログラムを作成する。 翻訳データのみから「EN.txt」を生成することもできるが、バージョンアップで「EN_.txt」が変更されることを考えて、書き換える方式にする。

スプレッドシートのスクリプト(Google Apps Script)を使うとデータを整形したり処理したりして出力できるが、今回はめんどくさいのでそのまま出力(csvでデータを「"」で囲みたいときなどはスクリプトを使う)。

翻訳データのタブを開き、「ファイル」->「形式を指定してダウンロード」->「タブ区切りの値」をクリック。下図のようにデータをtsv形式でダウンロードできる。

f:id:kengo700:20170307190731p:plain

このデータをプログラムで読み込む。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace InsertTextForHollowKnight
{
    class Program
    {
        static void Main(string[] args)
        {

            string translatedFile = @"C:\Temp\hollowknight\jp\Hollow Knight 日本語化作業所(仮) - 翻訳データ.tsv";

            Dictionary<string, string> translated_text_jp = new Dictionary<string, string>();

            // 翻訳データファイル(tsv形式)をオープン
            if (File.Exists(translatedFile))
            {

                using (StreamReader reader_translated = new StreamReader(translatedFile, Encoding.UTF8))
                {
                    // 1行目(見出し行)はスキップ
                    reader_translated.ReadLine();


                    // ファイル末尾まで、1行ずつ読み込み
                    while (reader_translated.EndOfStream == false)
                    {
                        string line = reader_translated.ReadLine();

                        char[] delimiterChars = { '\t' };
                        string[] words = line.Split(delimiterChars);

                        string temp_file = "";
                        string temp_id = "";
                        string temp_text_en = "";
                        string temp_text_jp = "";

                        int count = 0;
                        foreach (string word in words)
                        {
                            switch (count)
                            {
                                case 0:
                                    temp_file = word;
                                    break;
                                case 1:
                                    temp_id = word;
                                    break;
                                case 2:
                                    temp_text_en = word;
                                    break;
                                case 3:
                                    temp_text_jp = word;
                                    break;
                            }
                            count++;
                        }

                        Console.WriteLine("{0},{1}",temp_file, temp_id);

                        // データを変数に追加
                        translated_text_jp[temp_file+temp_id] = temp_text_jp;

                    }
                }

            }
        }
    }
}

「EN_*.txt」内のIDデータは全体で一意ではないっぽい(あるファイルと別のファイルで同じIDがある)のでファイル名とIDを組み合わせてDictionaryのキーにしてみている。

プログラムを実行すると、読み込めてそう。

f:id:kengo700:20170307192632p:plain

オリジナルデータも読み込み、翻訳データに書き換えた「EN_*.txt」を生成する。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Xml;

namespace InsertTextForHollowKnight
{
    class Program
    {
        static void Main(string[] args)
        {

            string translatedFile = @"C:\Temp\hollowknight\jp\Hollow Knight 日本語化作業所(仮) - 翻訳データ.tsv";
            string originalFile = @"C:\Temp\hollowknight\en\EN_Achievements.txt";
            string outputFile = @"C:\Temp\hollowknight\jp\EN_Achievements.txt";

            Dictionary<string, string> translated_text_jp = new Dictionary<string, string>();

            // 翻訳データファイル(tsv形式)をオープン
            if (File.Exists(translatedFile))
            {

                using (StreamReader reader_translated = new StreamReader(translatedFile, Encoding.UTF8))
                {
                    // 1行目(見出し行)はスキップ
                    reader_translated.ReadLine();


                    // ファイル末尾まで、1行ずつ読み込み
                    while (reader_translated.EndOfStream == false)
                    {
                        string line = reader_translated.ReadLine();

                        char[] delimiterChars = { '\t' };
                        string[] words = line.Split(delimiterChars);

                        string temp_file = "";
                        string temp_id = "";
                        string temp_text_en = "";
                        string temp_text_jp = "";

                        int count = 0;
                        foreach (string word in words)
                        {
                            switch (count)
                            {
                                case 0:
                                    temp_file = word;
                                    break;
                                case 1:
                                    temp_id = word;
                                    break;
                                case 2:
                                    temp_text_en = word;
                                    break;
                                case 3:
                                    temp_text_jp = word;
                                    break;
                            }
                            count++;
                        }

                        Console.WriteLine("{0},{1}", temp_file, temp_id);

                        temp_text_jp = temp_text_jp.Replace("'", "&#39;");
                        temp_text_jp = temp_text_jp.Replace("<", "&lt;");
                        temp_text_jp = temp_text_jp.Replace(">", "&gt;");

                        // データを変数に追加
                        translated_text_jp[temp_file + temp_id] = temp_text_jp;

                    }


                    // 元データファイルを開く
                    XmlDocument xmlDocument = new XmlDocument();
                    xmlDocument.Load(originalFile);

                    XmlNodeList nodeList = xmlDocument.SelectNodes(@"/entries/entry");

                    // 元データの保存用変数
                    List<string> original_id = new List<string>();

                    foreach (XmlNode node in nodeList)
                    {

                        XmlElement element = (XmlElement)node;
                        XmlAttribute attr_count = element.GetAttributeNode("name");

                        original_id.Add(attr_count.InnerText);

                    }


                    // 出力用ファイルを開く
                    using (StreamWriter writer = new StreamWriter(outputFile, false))
                    {

                        writer.WriteLine("<entries>");

                        for (int i = 0; i < original_id.Count(); i++)
                        {
                            writer.WriteLine("<entry name=\"{0}\">{1}</entry>", original_id[i], translated_text_jp[Path.GetFileName(originalFile) + original_id[i]]);
                        }

                        writer.WriteLine("</entries>");

                    }

                }
            }
        }
    }
}

やっぱりオリジナルデータを使わずに直接「EN_*.txt」を生成したほうが良かった気がする…

出力したファイルは下記の通り。ここでは「'」「<」「>」の文字参照への変換を確かめるために、tsvファイルの一部を「征服者の試しを'クリアする」に書き換えている。

f:id:kengo700:20170307201858p:plain

全ての「EN_*.txt」に対して同じ処理を実行。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Xml;

namespace InsertTextForHollowKnight
{
    class Program
    {
        static void Main(string[] args)
        {

            string translatedFile = @"C:\Temp\hollowknight\jp\Hollow Knight 日本語化作業所(仮) - 翻訳データ.tsv";

            string originalFolder = @"C:\Temp\hollowknight\en";
            string outputFolder = @"C:\Temp\hollowknight\jp";

            Dictionary<string, string> translated_text_jp = new Dictionary<string, string>();

            // 翻訳データファイル(tsv形式)をオープン
            if (File.Exists(translatedFile))
            {

                using (StreamReader reader_translated = new StreamReader(translatedFile, Encoding.UTF8))
                {
                    // 1行目(見出し行)はスキップ
                    reader_translated.ReadLine();


                    // ファイル末尾まで、1行ずつ読み込み
                    while (reader_translated.EndOfStream == false)
                    {
                        string line = reader_translated.ReadLine();

                        char[] delimiterChars = { '\t' };
                        string[] words = line.Split(delimiterChars);

                        string temp_file = "";
                        string temp_id = "";
                        string temp_text_en = "";
                        string temp_text_jp = "";

                        int count = 0;
                        foreach (string word in words)
                        {
                            switch (count)
                            {
                                case 0:
                                    temp_file = word;
                                    break;
                                case 1:
                                    temp_id = word;
                                    break;
                                case 2:
                                    temp_text_en = word;
                                    break;
                                case 3:
                                    temp_text_jp = word;
                                    break;
                            }
                            count++;
                        }

                        Console.WriteLine("{0},{1}", temp_file, temp_id);

                        temp_text_jp = temp_text_jp.Replace("'", "&#39;");
                        temp_text_jp = temp_text_jp.Replace("<", "&lt;");
                        temp_text_jp = temp_text_jp.Replace(">", "&gt;");

                        // データを変数に追加
                        translated_text_jp[temp_file + temp_id] = temp_text_jp;

                    }


                    List<string> originalFiles = new List<string>();

                    // ファイル名の取得(フルパス)
                    originalFiles.AddRange(Directory.EnumerateFiles(originalFolder, "*.txt", SearchOption.AllDirectories).ToList());

                    foreach (string originalFile in originalFiles)
                    {

                        // 元データファイルを開く
                        XmlDocument xmlDocument = new XmlDocument();
                        xmlDocument.Load(originalFile);

                        XmlNodeList nodeList = xmlDocument.SelectNodes(@"/entries/entry");

                        // 元データの保存用変数
                        List<string> original_id = new List<string>();

                        foreach (XmlNode node in nodeList)
                        {

                            XmlElement element = (XmlElement)node;
                            XmlAttribute attr_count = element.GetAttributeNode("name");

                            original_id.Add(attr_count.InnerText);

                        }

                        string outputFile = outputFolder + "\\" +  Path.GetFileName(originalFile);

                        // 出力用ファイルを開く
                        using (StreamWriter writer = new StreamWriter(outputFile, false))
                        {

                            writer.WriteLine("<entries>");

                            for (int i = 0; i < original_id.Count(); i++)
                            {
                                writer.WriteLine("<entry name=\"{0}\">{1}</entry>", original_id[i], translated_text_jp[Path.GetFileName(originalFile) + original_id[i]]);
                            }

                            writer.WriteLine("</entries>");

                        }


                    }

                }
            }
        }
    }
}

かなり雑なプログラムだけど、問題なさそう。

f:id:kengo700:20170307203101p:plain

日本語フォントの作成テスト3

次は漢字も含めたフォントを作ってみる。

「Bitmap font generator」は出力する文字をテキストデータを使って選択することができる。やり方と、日本語用のテキストデータは下記ページで。 ただし、このテキストデータはディレクトリ名に日本語が含まれない場所に置く必要がある。

出力設定もいくつか変えてみる。

f:id:kengo700:20170307205011p:plain

f:id:kengo700:20170307205022p:plain

f:id:kengo700:20170307205001p:plain

座標データは下記のような感じ。sizeがマイナスになっている理由は不明。

f:id:kengo700:20170307211105p:plain

バイナリデータを修正。試しに中文化版と同じように、サイズとかの部分を「F0 41」に統一してみる。

f:id:kengo700:20170307211136p:plain

テキストは作業所で多少修正したものを利用。したらバグった。

元のテキストと翻訳データを差し込んだテキストを比べてみると、改行コードが違う。

f:id:kengo700:20170307212253p:plain

なので出力部分をちょっと変える。

                            writer.Write("<entries>\n");

                            for (int i = 0; i < original_id.Count(); i++)
                            {
                                writer.Write("<entry name=\"{0}\">{1}</entry>\n", original_id[i], translated_text_jp[Path.GetFileName(originalFile) + original_id[i]]);
                            }

                            writer.Write("</entries>\n");

翻訳データを入れた「EN_*.txt」を全て使うとバグる。「EN_Elderbug.txt」だけなら問題なく日本語化できた。1つずつ試してみて、原因を探る。

「EN_Credits List.txt」を入れるとバグる。元データで「&」となっている部分が「&」になっているからっぽい。

変換部分を追記する。

                        temp_text_jp = temp_text_jp.Replace("'", "&#39;");
                        temp_text_jp = temp_text_jp.Replace("<", "&lt;");
                        temp_text_jp = temp_text_jp.Replace(">", "&gt;");
                        temp_text_jp = temp_text_jp.Replace("&", "&amp;");

「"」の変換も加える。

                        temp_text_jp = temp_text_jp.Replace("\"", "&quot;");

ID部分にも文章っぽい文字が入っている場合があることを忘れていた。この部分も変換処理をする。

                            for (int i = 0; i < original_id.Count(); i++)
                            {

                                string temp_id = original_id[i].Replace("'", "&#39;");
                                temp_id = temp_id.Replace("<", "&lt;");
                                temp_id = temp_id.Replace(">", "&gt;");
                                temp_id = temp_id.Replace("&", "&amp;");
                                temp_id = temp_id.Replace("\"", "&quot;");

                                writer.Write("<entry name=\"{0}\">{1}</entry>\n", temp_id, translated_text_jp[Path.GetFileName(originalFile) + original_id[i]]);
                            }

f:id:kengo700:20170307232138p:plain

f:id:kengo700:20170307232220p:plain

おっけー。

「し」とかの文字がちょっとかすれて見えるのは、ビットマップフォント作成時のスムージングの影響っぽい。

「Font smoothing」のチェックを外すと、生成されるフォントが少し変わる。

f:id:kengo700:20170307235027p:plain

チェックを入れた場合

f:id:kengo700:20170307235246p:plain

チェックを外した場合

f:id:kengo700:20170307235232p:plain

まあ、細かい調整は最後にやろう。

日本語フォントの作成テスト4

文字の表示位置が下にずれているのを修正する。 会話文であれば違和感はないが、インベントリではずれてかぶってしまう。

f:id:kengo700:20170308001021p:plain

上の図で気付いたが、ビットマンとフォントの設定を間違って「Noto Sans CJK KR DemiLight」 を選んでしまっている。「Noto Sans CJK JP DemiLight」を選んだつもりだった…

これはテストのためだけのフォントで、後々もっとおしゃれなフォントを探すつもりだけど、気になるのでビットマップフォントを作り直し。

f:id:kengo700:20170308002800p:plain

とりあえず前に解析した結果から、座標データファイルの中のBaseの部分をゼロにしてみる。

f:id:kengo700:20170308003411p:plain

するとずれが解消された。

f:id:kengo700:20170308003457p:plain

根本的な原因が分からず、対処療法的でとてもすっきりしないが、とりあえずこれでいいとしておく。

日本語フォントの作成テスト5

これまでは「perpetua_tmpro Atlas.tex.dds」と「resources_00002.-5」しか日本語化していないので、他のフォントも日本語化する。とりあえず同じフォントデータを使いまわす。

f:id:kengo700:20170308012418p:plain

f:id:kengo700:20170308014437p:plain

いちいちUnityEXを起動するのがめんどいのでバッチファイルでリパックする。

for %%a in (*.assets) do UnityEX.exe import "%%a"
pause

これまで表示されなかったオブジェクトとのインタラクトの文字が表示されるようになったが、縦方向がずれてしまっている。やっぱり何かしらフォントの座標データファイルの解析が間違っているっぽい。

f:id:kengo700:20170308023757p:plain

baseの部分を41F0(float、30)にしたらさらに下がった。C1F0(float、-30)にしても上には上がらず。 やはり根本の原因を探らないと解決できないようだ。

フォントデータの解析7

座標データファイル内で、中文化版ではゼロになっていたのでゼロにしていた部分をマイナスにしてみる。C1F0(float、-30)にすると、少し上がった。

f:id:kengo700:20170308030659p:plain

f:id:kengo700:20170308030634p:plain

C220(float、-40)にするともう少し上がったが、C248(float、-50)にしてもこれ以上は上がらなかった。

f:id:kengo700:20170308030942p:plain

3/8~

そもそもそういえば、先日フォントを作成したときは「あ」と「い」の高さもずれていたので、「Equalize the cell heights」のオプションでフォントの高さを合わせていたのだった。このオプションを外してフォントを作成してみる。

f:id:kengo700:20170308194546p:plain

f:id:kengo700:20170308194846p:plain

ゲーム内では、やはりy座標の設定がおかしいようで、位置がバラバラになっている。

ためしに「の」の字の座標データを書き換えてみる。

f:id:kengo700:20170308195422p:plain

このyoffsetを13から100に書き換える。

f:id:kengo700:20170308195521p:plain

f:id:kengo700:20170308200147p:plain

結果、上方向に移動した。yoffsetはプラスで下だと思っていたけど逆のようだ。

座標データ変換ソフトでyoffsetをマイナスにしてみる。

            for (int i = 0; i < count; i++)
            {
                yoffset[i] = yoffset[i] * (float)-1.0;
            }

f:id:kengo700:20170308205335p:plain

ずれが解消された。

オブジェクトとのインタラクト関連のフォントのファイル「sharedassets0」に同じフォント画像と座標データを適用する。昨日マイナスにした部分はゼロに戻しておく。

f:id:kengo700:20170308210235p:plain

f:id:kengo700:20170308210456p:plain

やっぱりずれている。ゼロにした部分をC248(float、-50)に戻す。

f:id:kengo700:20170308211004p:plain

f:id:kengo700:20170308210939p:plain

模様と被らないようになった。 対処療法に対処療法を重ねた感じだけれど、これでいいとしてしまおう。 yoffsetがプラスマイナス逆だったのでxoffsetも怪しいけれど、そちらも問題が出てから調べる。

フォント探し

フリーで再配布も可能でゲームの雰囲気にあったフォントを探す。

オリジナルの英語のフォントは、前述の通り「Trajan」「Perpetua」「Arial」。

「Trajan」はメニューなどに使われているフォント。「Q」の字がカッコいい。

f:id:kengo700:20170308211422p:plain

「Perpetua」は会話文などに使われているフォント。読みやすさ重点。

f:id:kengo700:20170308211703p:plain

というわけでスタイリッシュなフォントと、細めで読みやすいフォントを探してみる。ついでに海底探索ゲーム『Diluvion』用のフォントも探す。

IPAフォントはビットマップフォント状態での配布はできないらしい。多くの明朝体のフリーフォントがIPAフォントの漢字を利用しているので、それらのフォントはビットマップフォントとしては使えない。

パワーポイントでいろいろ比べてみる。

  • 花園明朝A
  • 花園明朝B
  • 源真ゴシック Normal
  • 源真ゴシックP Light
  • 02うつくし明朝体
  • あおぞら明朝 Regular
  • 飴鞭ゴシック-01
  • 刻明朝 Regular
  • はんなり明朝
  • IPAex明朝
  • Noto Sans CJK JP Light

f:id:kengo700:20170308231756p:plain

「飴鞭ゴシック」など、あまり特徴的すぎると主張が激しくなりすぎる。ゴシックのフォントは読みやすいものの、ちょっとポップな感じが出てしまう。 試した中では、メニューなどはIPAex明朝、その他は花園明朝Aが良さげか。

メニューなどの部分はダイナミックフォントなので、フォントを作成しなくてもすぐ試せる。下記のようにファイルを配置し、リパックする。

  • resources.assets
  • UnityEX.exe
  • Unity_Assets_Files
    • resources
      • TrajanPro-Bold.ttf(←IPAex明朝のipaexm.ttfをリネーム)
      • TrajanPro-Regular.ttf(←IPAex明朝のipaexm.ttfをリネーム)
      • Perpetua.ttf(←花園明朝AのHanaMinA.ttfをリネーム)

f:id:kengo700:20170308232506p:plain

すごく良い…

f:id:kengo700:20170308233828p:plain

実績画面は、実績の名前がIPAex、実績の説明が花園明朝になっている。どちらも癖の少ない明朝体なのであまり違いは分からないが、とりあえず両方使う方針で。

ちなみに『Diluvion』の方は「刻明朝 Regular」と「Noto Sans CJK JP Bold」が良さげ。英語の「BirchStd Font」と似ていてしっくりくる。

f:id:kengo700:20170309015232p:plain

これを「IPAex明朝」にしてみると、間延びしてしまって違和感が大きい。悪くはないけど。

f:id:kengo700:20170309015455p:plain

日本語フォントの作成

作成したプログラムを使用して、日本語(花園明朝A)のビットマップフォントを作成する。

まずは「perpetua_tmpro Atlas.tex.dds」と「resources_00002.-5」だけ修正。sizeの部分は00004248に。

f:id:kengo700:20170309002614p:plain

f:id:kengo700:20170309002844p:plain

少しカッチリしすぎて浮いているような気がする。ふちがもう少しだけぼやけてほしい。

出力設定の「Render from True Type outline」のチェックを外してみる。

f:id:kengo700:20170309004035p:plain

ぼやけはしてくれなかったけど、前より少しだけカスレが改善されて見やすくなった。

「ClearType」のチェックを外してみる。

f:id:kengo700:20170309005201p:plain

少しだけ文字が太く、さらにカスレが改善した。

「Super sampling」をlevel4にしてみる。

f:id:kengo700:20170309010312p:plain

カスレが悪化してしまった。ひとつ前のものの方がよさそう。

というわけで下記の設定で行くことにする。

f:id:kengo700:20170309011018p:plain

f:id:kengo700:20170309011033p:plain

日本語フォントの作成2

3/9~

残りのファイルを修正する。

f:id:kengo700:20170309191603p:plain

リパック中、Steamクライアントを起動したら『Hollow Knight』がアップデートした。

テキストデータのアップデート箇所のチェック

念のためSteamクライアントから「ゲームキャッシュの整合性を確認」を実行。「resources.assets」と「sharedassets0.assets」を取り出して、「UnityEX.exe」と下記内容の「export_txt_all.bat」でテキストデータを抜き出す。

for %%a in (*.assets) do UnityEX.exe export "%%a" -t txt
pause

アンパックしたtxtファイルの内EN_*.txtを別フォルダにコピーし、自作した「ExtractTextForHollowKnight」プログラムでデータをtsv形式の一つのファイルにまとめる。

そのファイルと、前回作成したファイルを「WinMerge」に取り込み、比較する。

f:id:kengo700:20170309192619p:plain

「表示」メニューの「Diffコンテキスト」を0にすると変更部分のみが表示されるため、この状態で表示されているテキストを「TeraPad」などにコピペすれば変更行数が分かる。

f:id:kengo700:20170309192806p:plain

今回は32テキストのみの変更。これぐらいなら手動で作業所をメンテナンスできるものの、今後のことを考えて、アプデに対応してtsvファイルを更新するプログラムを作るべきかもしれない。

作業所の更新

今回は手動で更新する。

作業所を開き、「ツール」->「スプリプトエディタ」から先日作成したバックアップスクリプトを実行しておく。

あとは「WinMerge」の比較画面や元ファイルを確認しながら、一つずつ編集する。

終わったら作業所の履歴部分に追記しておく。

f:id:kengo700:20170309195546p:plain

ゲームプログラムのアップデート箇所のチェック

下記ファイルを、アプデ前とアプデ後と中文化版のものを比較する。場合によっては中文化MODの更新を待つ必要がでてくる。

  • resources\resources_00001.-2
  • resources\resources_00001.-20
  • resources\resources_00001.-5
  • resources\resources_00002.-5
  • resources\resources_00003.-5
  • resources\resources_00004.-5
  • sharedassets0\sharedassets0_00001.-1

まずは「resources_00001.-2」。Stirlingで開いて「検索・移動」->「比較」でアプデ前とアプデ後、アプデ前と中文化版をそれぞれ比較する。

f:id:kengo700:20170309200902p:plain (上からアプデ前、アプデ後、中文化版)

これは…無理かもしれぬ…。このファイルを修正するにはUnityの知識が必須っぽい。

次は「resources_00001.-20」

f:id:kengo700:20170309202427p:plain (上からアプデ前、アプデ後、中文化版)

アプデ前とアプデ後よりも、アプデ後と中文化版のほうが近いのはなぜだろうか…

思い付きで、中文化版の「resources_00001.-2」と「resources_00001.-20」を除いたビットマップフォントの画像と座標データと「EN_*.txt」だけを、v1.0.0.7の「resources」と「sharedassets0」にリパックしてゲームを起動してみる。

f:id:kengo700:20170309211539p:plain

f:id:kengo700:20170309211407p:plain

問題なく中文化できた。ゲームを進めたら問題が生じるかもしれないが、とりあえず「resources_00001.-2」と「resources_00001.-20」は使わずに日本語化MODを作成してみることにする。

次は「resources_00001.-5」を比較。座標データなので中文化版では変わっているはず。アプデ前とアプデ後だけを比較する。

f:id:kengo700:20170309212034p:plain

変更箇所は1箇所だけ。ここはオリジナル版と中文化版で変化していなかったので無視していた部分。今後の作業ではここだけアプデ後版の値に変えておこう。

次は「resources_00002.-5」。変更箇所は2箇所。

f:id:kengo700:20170309212400p:plain

f:id:kengo700:20170309212425p:plain

次は「resources_00003.-5」。変更箇所は1箇所。

f:id:kengo700:20170309212549p:plain

次は「resources_00004.-5」。変更箇所は1箇所。

f:id:kengo700:20170309212646p:plain

次は「sharedassets0_00001.-1」。変更箇所はなし。

日本語フォントの作成3

作成中だったファイルの5箇所を修正して、v1.0.0.7の「resources」と「sharedassets0」にリパック。前節で中文化版では修正しなくても動いていたので、修正しなくてもいいかもしれない。

f:id:kengo700:20170309213440p:plain

f:id:kengo700:20170309214218p:plain

f:id:kengo700:20170309214510p:plain

そういえば「EN_*.txt」はv1.0.0.7用に作り直し忘れていたけど、問題はなさげ。

フォントのサイズや行間などを微調整。

f:id:kengo700:20170309221956p:plain

f:id:kengo700:20170309222041p:plain

f:id:kengo700:20170309222057p:plain

f:id:kengo700:20170309222119p:plain

やっぱりちょっとだけ字がカッチリし過ぎていて浮いている気もするけれど、なかなかいい感じだと思う。 日本語フォント作成は、とりあえずこれでひと段落。

日本語Wikiの立ち上げ

翻訳作業の注意事項をまとめたり、翻訳内容について議論する場所を確保するため、日本語wikiを立ち上げることにする。

スプレッドシートは情報をまとめたり議論したりするには向いていないと思うので、作業所のほかに情報をまとめる何かしらのウェブページを作ったほうが良いと思う。

今回はSteamガイドかブログの記事を作ろうかと思っていたものの、Steamガイドは検索に引っかかりにくいこと、ブログの記事のコメント欄は議論に向かないことから、Wikiを立ち上げることにした。 それと、ブログは個人のものというイメージが強いので、ブログ記事で取りまとめられている有志翻訳には参加しにくいのではないかとも思った。

Wikiは自前で用意することもできるが、Wikiサービスを利用したほうが簡単。今回は「WIKIWIKI.jp」というサービスを利用することにする。

WIKIWIKIで新しいWikiページを立ち上げる方法は下記の通り。

  • 下記ページの「新規登録する」をクリック
  • 必要事項を記入し「利用規約に同意し新規登録する」をクリック
    • WIKIWIKI ID:Wikiのurlになる。後から変更不可
    • WIKIタイトル:Wikiのタイトル。後から変更できるかは忘れた
    • デザインテンプレート:Wikiの見た目。後から変更可 f:id:kengo700:20170309224655p:plain
  • メールが送られてくるので、記載されたurlを開く

以上でWikiページの作成が完了

f:id:kengo700:20170309224850p:plain

デフォルトではWikiの使い方などのリンクが書かれているので、ゴリゴリ書き換えていく。

日本語Wikiの編集

ページを編集するには、左上の「編集」をクリックし書き換えて「ページの更新」をクリックする。 凍結されているページを編集するには登録したパスワードが必要。

とりあえずトップページを、以前立ち上げた『Owlboy』のWikiを参考に編集。

#br

&size(20){''Hollow Knight JP Wiki へようこそ!''};

このWikiは2Dアクションゲーム「Hollow Knight」に関する情報サイトです。誰でも自由に編集できます。 

#br

&flash(http://www.youtube.com/v/UAO2urG23S4,682x408);

#br

* お知らせ [#f881edfa]

**2017/02/25 [#q560f468]

-Hollow Knightリリース!

サイドバーを編集するには、上の「一覧」アイコンをクリックし、「MenuBar」ページを開いて編集する。

トップの文字を画像に変えるには、フロントページの「添付」から画像をアップロードし、「新規作成」で「:Header」を作成して編集する。

CENTER:[[&attachref(FrontPage/logo_main.png,nolink, nolink,30%, トップ画像);:http://wikiwiki.jp/hollowknight/?FrontPage]]

結果、こんな感じに。

f:id:kengo700:20170309231832p:plain

ダークな感じにしたいので、デザインテンプレートを変更する。やり方は下記ページ。

今回は「default_black03_z」を選んだ。設定画面でフォントも変えられるみたいだが、試しに「明朝」にしてみたところ読みにくかったので「ゴシック」に戻した。明朝の方が雰囲気はいいんだけど…

f:id:kengo700:20170309232415p:plain

なかなかいい感じ。

日本語Wikiの編集2

Wikiを立ち上げた目的である有志翻訳に関するページを作る。

新しいページを作るには左上の「新規」をクリックするか、ページ編集で新しいページタイトルというリンクを書き込み、それをクリックする。

だいたいこんな感じか。

日本語化についてと掲示板のページも作った。

翻訳作業

3/10~

やっぱりプレイしてエリアやボスのビジュアルを確認しないと翻訳できないな…

もうちょっとだけ進めたら翻訳に戻ろう…次のベンチまで…

テキストデータのアップデート箇所のチェック2

3/14~

v1.0.1.4へのアップデートが来たので確認。

テキストについては修正10、追加3。

f:id:kengo700:20170314224232p:plain

作業所のデータを手動で修正。

ちなみにゲームの進捗はたぶん中盤ぐらい。 毎日ちまちま進めているところ。

生涯一度の初見プレイはじっくりと楽しまなければ。

それと連続してプレイしていると疲れてきて、ミスが増えて進めなくなる…(疲れていなければミスしないとは言っていない)。

3/17~

ゲームクリア!

翻訳は明日から本気出す。

ゲームプログラムのアップデート箇所のチェック2

3/18~

前回のアプデと同様に、下記ファイルのv1.0.0.7のものとv1.0.1.4のものと比較する。

  • resources\resources_00001.-5
  • resources\resources_00002.-5
  • resources\resources_00003.-5
  • resources\resources_00004.-5
  • sharedassets0\sharedassets0_00001.-1

上がv1.0.0.7、下がv1.0.1.4。sharedassets0_00001.-1については変更箇所なし。

f:id:kengo700:20170318183636p:plain

f:id:kengo700:20170318183716p:plain

f:id:kengo700:20170318183741p:plain

f:id:kengo700:20170318183819p:plain

f:id:kengo700:20170318183910p:plain

変更箇所は前回と全く同じ。前回と同様に、v1.0.0.7からv1.0.1.4への変更箇所部分だけ日本語フォントの座標データのファイルを手動で修正。v1.0.1.4のresources.assetsとsharedassets0.assetsにリパック。

f:id:kengo700:20170318185624p:plain

f:id:kengo700:20170318185705p:plain

問題なさげ。

現在の作業所のデータを使ってみる。tsvファイルにエクスポートし、自作のプログラムで「EN_*.txt」を作成。resources.assetsへリパック。

↓ちょっとネタバレ注意

[ジャーナルの一部(クリックで表示)]

f:id:kengo700:20170318190914p:plain

[インベントリ(クリックで表示)]

f:id:kengo700:20170318191237p:plain

[全体マップ(クリックで表示)]

f:id:kengo700:20170318191010p:plain

やっぱり一部文字がかすれてしまっているけど、いい感じ。

ある程度ゲームをプレイしてみたけど、問題は起きなかった。Hiveへの入り方が分からねえ…

日本語テストパッチの作成

翻訳としては正しいものでもゲーム画面に表示してみると違和感が出るものもあるので、私以外の人も作業所のデータを元に日本語化できるように準備する。

とりあえず下記のようなファイルを用意すればいいか。

  • README.txt(←説明)
  • リパック.bat(←tsvファイルからtxtの作成と、リパックの作業を一括で行うバッチファイル)
  • Hollow Knight 日本語化作業所(仮) - 翻訳データ.tsv(←翻訳データ。各自が作業所からエクスポートして上書きする)
  • Unity_Assets_Files
    • resources
      • resources_00001.-5(日本語フォントの座標データ)
      • resources_00002.-5(日本語フォントの座標データ)
      • resources_00003.-5(日本語フォントの座標データ)
      • resources_00004.-5(日本語フォントの座標データ)
      • Textures
        • perpetua_tmpro Atlas.tex.dds(日本語フォントの画像データ)
        • Perpetua-SDF Atlas.tex.dds(日本語フォントの画像データ)
        • trajan_bold_tmpro Atlas.tex.dds(日本語フォントの画像データ)
        • res_logo
          • ARIAL SDF Atlas.tex.dds(日本語フォントの画像データ)
    • sharedassets0
      • sharedassets0_00001.-1(日本語フォントの座標データ)
      • Textures
        • TrajanPro-Bold SDF Atlas.tex.dds(日本語フォントの画像データ)
  • program
    • InsertTextForHollowKnight.exe(←自作のプログラム)
  • originaltext_v1.0.1.4
    • EN_*.txt(←オリジナルのテキストファイル)
  • license(←配布するフォントのライセンス情報)
    • IPAex明朝
    • 花園明朝

resources.assets、sharedassets0.assetsとUnityEX.exeは各自で用意してもらい、「リパック.bat」を実行すると下記の処理が行われるようにする。

  1. InsertTextForHollowKnight.exeを実行し、オリジナルのEN.txtと翻訳データの.tsvから翻訳済みのEN*.txtを生成する
  2. UnityEX.exeを実行し、resources.assetsとsharedassets0.assetsにデータをリパックする

これを実現するために翻訳データの挿入プログラムを修正する。

プログラム内で決め打ちにしていたファイル名やフォルダ名の部分を引数で与えるようにする。

            //string translatedFile = @"C:\Temp\hollowknight\jp\Hollow Knight 日本語化作業所(仮) - 翻訳データ.tsv";
            //string originalFolder = @"C:\Temp\hollowknight\en_v1.0.1.4";
            //string outputFolder = @"C:\Temp\hollowknight\jp";

            if (args.Length >= 3)
            {

                string translatedFile = args[0];
                string originalFolder = args[1];
                string outputFolder = args[2];

                (中略)
            }

そして下記のバッチファイルを作成して実行すると、問題なくEN_*.txtが生成された。

InsertTextForHollowKnight.exe "C:\Temp\hollowknight\jp\Hollow Knight 日本語化作業所(仮) - 翻訳データ.tsv" "C:\Temp\hollowknight\en_v1.0.1.4" "C:\Temp\hollowknight\jp"
pause

ファイルが見つからなかった時のエラー表示とか全くしてないけど、まあいいか…

前述のフォルダを用意し、下記のバッチファイルを実行すると、問題なくEN*.txtが生成された。翻訳データ(tsvファイル)のファイル名が変わってもいいように、同じフォルダ内のtsvファイル全部に対して同じ処理をしている。ただし複数のtsvファイルをマージできるわけでなく、生成されるEN*.txtは最後に処理したtsvファイルのものになる(こんな変なことをしているのは、バッチファイルの書き方の知識が無くて、前に作ったバッチファイルを修正しているから。もっとスマートな書き方はあるはず…)。

for %%a in (*.tsv) do ".\program\InsertTextForHollowKnight.exe" "%%a" ".\original_text\en_v1.0.1.4" ".\Unity_Assets_Files\resources"
pause

リパック処理も加える。

for %%a in (*.tsv) do ".\program\InsertTextForHollowKnight.exe" "%%a" ".\original_text\en_v1.0.1.4" ".\Unity_Assets_Files\resources"
for %%a in (*.assets) do UnityEX.exe import "%%a"
pause

もろもろのファイルを準備。

f:id:kengo700:20170318212842p:plain

いったんゲームを英語版に戻し、準備したファイルを利用して日本語化できることを確認。

日本語テストパッチの配布

配布するために「Lhaplus」で圧縮し、Googleドライブにアップロード。

f:id:kengo700:20170318213121p:plain

ファイルを右クリックし、「共有」から作業所の共有と同じように公開設定にする。ただしアクセスは「編集者」ではなく「閲覧者」に。

日本語化作業所にリンクを記載

f:id:kengo700:20170318213933p:plain

Googleアカウントからログアウトしてダウンロードできることを確認。 READMEのフォントのところに、誤字というか表記揺れが…

これで問題がなければ、後はひたすら翻訳するだけだ…

文字がかすれる問題については、ビットマップフォントを作るときにボールドにすればいいかもしれないと思いついたのでメモ。 時間があるときにやってみよう。

表示がおかしい部分のチェック(途中)

日本語化すると表示がおかしい部分をチェックする。

ジャーナルの左の文字が下にずれている? 右上の表記で名前が長くて二行になると被ってしまう。 ここら辺はもう少し文字を小さくするか、文字間隔が狭いフォントを使いたいところ。

f:id:kengo700:20170319164635p:plain

キーボード操作だと、マップ画面の右下のキーが表示されない(コントローラだと表示される)。

f:id:kengo700:20170319164710p:plain

翻訳作業2

3/26~

ひたすら翻訳。

息抜きにこれまでの進捗をグラフにしてみる。 なぜが26日のバックアップが取れてなかった…

f:id:kengo700:20170326173545p:plain

皆さまお疲れ様です。

そろそろフォントの修正もしなければ…

3/27~

やっぱり最後までプレイしないと翻訳できないな…

なんだこのノコギリ…

3/29~

やっとラスボスに勝ったー

なん…だと…

各フォントの使用箇所の調査

フォントの不備を修正する際に、5種類あるファイル(resources_00001.-5、resources_00002.-5、resources_00003.-5、 resources_00004.-5、sharedassets0_00001.-1)のどれを修正すればいいか分からないので、どのフォントがゲーム内のどこで使われているかを調査する。

判別のために、ビットマップフォント画像に色を付けてみたが、ゲーム内では結局白色で表示されてしまった。

そこで、わかりやすい太字のゴシックのビットマップフォントを作成し、一つずつ差し替えて確認することにする。

f:id:kengo700:20170329151850p:plain

f:id:kengo700:20170329151944p:plain

作成したフォントの座標データを自作プログラムでバイナリ形式に変換し、フォントファイルの座標データと文字数部分を書き換える。

f:id:kengo700:20170329151823p:plain

とりあえず作成ミスがないか確かめるため、全部を新しいフォントにしてテスト。

f:id:kengo700:20170329181441p:plain

問題なさそう。

各フォントを一つずつゴシックに差し替え、ゲーム序盤部分と終盤部分で簡単に確かめられる部分の文字をチェックする。

先ずは「perpetua_tmpro Atlas.tex.dds」「resources_00002.-5」をゴシックに差し替え。

オープニングの本文部分だけ
f:id:kengo700:20170329183757p:plain

最初のクレジットの文字、操作のチュートリアル(ずれてる)
f:id:kengo700:20170329183902p:plain

石碑の文字
f:id:kengo700:20170329183950p:plain

会話文
f:id:kengo700:20170329184025p:plain

インベントリのほとんどの文字(ただし数字部分は別フォント)
f:id:kengo700:20170329185532p:plain

なぜか地図の「ダートマス」などは別のフォント(図はネタバレ防止のため一部黒塗り)
f:id:kengo700:20170329184215p:plain

次に「Perpetua-SDF Atlas.tex.dds」「resources_00001.-5」をゴシックに差し替え。

ざっとプレイした限りでは、使用されている箇所は見つからず。

次に「trajan_bold_tmpro Atlas.tex.dds」「resources_00003.-5」をゴシックに差し替え。

オープニングの下の部分だけ
f:id:kengo700:20170329191045p:plain

エリアタイトル(少し大きすぎるか、ずれている)
f:id:kengo700:20170329191116p:plain

話者の名前部分
f:id:kengo700:20170329191205p:plain

地図のエリア名
f:id:kengo700:20170329191252p:plain

一部のアイテムの個数
f:id:kengo700:20170329191334p:plain

次に「ARIAL SDF Atlas.tex.dds」「resources_00004.-5」をゴシックに差し替え。

ざっとプレイした限りでは、使用されている箇所は見つからず。

最後に「TrajanPro-Bold SDF Atlas.tex.dds」「sharedassets0_00001.-1」をゴシックに差し替え。

コントローラーうんぬん
f:id:kengo700:20170329192514p:plain

オブジェクトの動作表記
f:id:kengo700:20170329192557p:plain

位置がずれているかどうかの比較と、英語版ではどんな雰囲気なのかをチェックする為、各画像のシーンの元のバージョンと花園明朝Aのバージョンのスクショを取ってみる。 ついでに翻訳データを作業所からダウンロードして反映。

f:id:kengo700:20170329193354p:plain:w325f:id:kengo700:20170329201214p:plain:w325

元バージョンと同じように、フォントを2種類使ったほうがいいかもしれない。
f:id:kengo700:20170329193407p:plain:w325f:id:kengo700:20170329201324p:plain:w325

他の部分はずれてないのに「調べる」だけずれてる意味が分からぬ…
f:id:kengo700:20170329193418p:plain:w325f:id:kengo700:20170329201341p:plain:w325

新スキルの説明文が2ページに渡ってしまっているのがすこし微妙。
f:id:kengo700:20170329193430p:plain:w325f:id:kengo700:20170329201400p:plain:w325

明朝体だとあまり違和感はないが、やっぱり少し小さくすべきか。
f:id:kengo700:20170329193456p:plain:w325f:id:kengo700:20170329201423p:plain:w325

会話文の方も少し小さくした方がいいかもしれないけど、アルファベットより日本語の方が大きくした方が読みやすいはずなので、どうしたものか。
f:id:kengo700:20170329193519p:plain:w325f:id:kengo700:20170329201447p:plain:w325

f:id:kengo700:20170329193533p:plain:w325f:id:kengo700:20170329201508p:plain:w325

他の部分はずれてないのに、左の敵の名前だけずれてる意味が分からぬ… f:id:kengo700:20170329193543p:plain:w325f:id:kengo700:20170329201523p:plain:w325

日本語フォントの改善

どこから手を付けるべきか悩むが、とりあえず前に思いついた通り、文字のカスレを解消するために花園明朝の太字を試してみる。

下記の設定で作ってみる。

f:id:kengo700:20170329203119p:plain

試してみると、思ったよりボールドっぽさもなく、視認性が向上していい感じ(また昔の翻訳データを使ってしまったので、文章がちょっと違う)。 これなら、もう少し文字を小さくしても大丈夫かも。

f:id:kengo700:20170329205655p:plain

f:id:kengo700:20170329205637p:plain

文字のサイズを少し小さくしてみる。

前回の設定では、文字サイズを41F0(float、30)と4220(float、40)の2種類使っている。理由は忘れた。 とりあえず全部4248(float、50)にしてみる(数値が大きいほど文字は小さくなる)。

f:id:kengo700:20170329211108p:plain

ちょっと小さすぎた。
f:id:kengo700:20170329212439p:plain

石碑の文字は1ページに収まるようになった。このくらいの文字サイズであれば、いいかんじか。
f:id:kengo700:20170329212507p:plain

エリアタイトルがちっさい!
f:id:kengo700:20170329212607p:plain

会話文は問題なし。
f:id:kengo700:20170329212640p:plain

地図の文字は明らかに小さすぎる。 f:id:kengo700:20170329212753p:plain

文字サイズを試行錯誤しつつ調整。 最終的にresources_00003.-5は420C(float、35)、sharedassets0_00001.-1は4220(float、40)、その他は4234(float、45)にした。

f:id:kengo700:20170329215856p:plain

f:id:kengo700:20170329220236p:plain

f:id:kengo700:20170329220222p:plain

インベントリの文の行間が詰まりすぎている気がしたので、resources_00002.-5行の高さの部分を4234(float、45)に変更。

f:id:kengo700:20170329223229p:plain

あんまり行の高さを大きくしすぎると、会話文が微妙になるので、これくらいの調整で。

f:id:kengo700:20170329223352p:plain

f:id:kengo700:20170329223339p:plain

文字がずれていた2箇所(操作チュートリアル部分とハンター日誌の敵の名前)を直すために、ダメもとで、以前「聞く」のずれを直したのと同じ部分を C248(float、-50)に変えてみる。

f:id:kengo700:20170329225340p:plain

直った!
f:id:kengo700:20170329225421p:plain

直った!! f:id:kengo700:20170329225438p:plain

なんで他の部分は逆にずれないのか分からぬ…

日本語フォントの改善2

英語版がフォントを2種類使い分けているので、日本語フォントも追加したい。特にエリアタイトルの部分が、ちょっと微妙。悪くはないけど…

まあ追加して微妙だったら元に戻せばいいだけなので、とりあえずやってみる。

といっても、ビットマップフォントとして再配布できるフォントの種類は限られている。候補は「飴鞭ゴシック」と「Noto Sans CJK JP」くらいか。

f:id:kengo700:20170329231146p:plain

パワポで見比べてみてもどっちがいいか分からんので、両方作ってみる。

f:id:kengo700:20170329231240p:plain

f:id:kengo700:20170329231454p:plain

trajan_bold_tmpro Atlas.tex.ddsとresources_00003.-5を差し替え。

↓飴鞭ゴシック

f:id:kengo700:20170329234346p:plain

f:id:kengo700:20170329234405p:plain

f:id:kengo700:20170329234418p:plain

↓Noto Ans CJK JP

f:id:kengo700:20170329234432p:plain

f:id:kengo700:20170329234444p:plain

f:id:kengo700:20170329234504p:plain

花園明朝と飴鞭ゴシックとNoto Sansでは飴鞭ゴシックが一番いいかな…? 完全に好みの問題のような気もする。

あと、英語だとしっかり縁どられているのに日本語だと縁取りがうすいのはなんでだろうか。フォント側の問題なのか。

英語のビットマップフォントはだいぶぼやけているので、この部分が黒縁になっているのかもしれない。

f:id:kengo700:20170330000517p:plain:w60

f:id:kengo700:20170330000903p:plain:w60

Bitmap font generatorの設定を眺めていたら、アウトラインの設定があるじゃん、と気付く。

f:id:kengo700:20170330003542p:plain

公式ページの説明によると、白文字に黒縁は下記の設定らしい。

f:id:kengo700:20170330003628p:plain

それっぽい
f:id:kengo700:20170330003747p:plain

ちがう、そうじゃない
f:id:kengo700:20170330003827p:plain

英語のビットマップフォントと実際のゲームの文字を比べてみると、ぼやけているのはアウトラインではないっぽい。文字の太さが全然違う。

いろいろググってみたところ、「Distance Field」という技術があるらしい。これっぽいかな。

距離フィールドテクスチャの作成

3/30~

試しに作ってみる。ありがたいことに日本語の解説ページがあるので、参考にしつつ。

先ずは必要なソフトをダウンロード。下記ページから「libgdx-nightly-latest.zip 」をダウロードした。

Lhaplusで解凍したらエラーが出たのでWinRARで解凍。

コマンドラインで下記コマンドを実行したところ、既にjavaはインストールされている。

java -version

上記ページのコマンドではなぜか起動しなかったので、ググって下記ページのコマンドを実行したら起動した。Javaはよく知らんので、原因は分からぬ…

java -cp gdx.jar;gdx-natives.jar;gdx-backend-lwjgl.jar;gdx-backend-lwjgl-natives.jar;extensions\gdx-freetype\gdx-freetype.jar;extensions\gdx-freetype\gdx-freetype-natives.jar;extensions\gdx-tools\gdx-tools.jar com.badlogic.gdx.tools.hiero.Hiero

f:id:kengo700:20170330155607p:plain

設定は下記ページなどを見て試行錯誤

とりあえず試しでエリアタイトルだけのフォントを作ってみる。

f:id:kengo700:20170330163039p:plain

これまで作成したビットマップフォントは4096x4096だったのに対し、このソフトは2048x2048までしか作れないっぽい? なのでこれまでと同じフォントサイズだと1枚に入りきらない。サイズを小さくするか、文字数を絞る必要がある。最終的には、翻訳が終わった後にその文字データを突っ込んで、使っている文字だけのフォントを作成するべきか。

とりあえず、この方法で試しに作ってみて、大丈夫と分かってから考えよう。

作成したものを保存すると、fntとpngファイルが生成された。

f:id:kengo700:20170330163631p:plain

それっぽい
f:id:kengo700:20170330163916p:plain

座標データの方は、Bitmap font generatorの場合と似ているが、xml形式ではない。つまりバイナリへの変換プログラムを作り直さねばならぬ… f:id:kengo700:20170330163951p:plain

とりあえずファイルを読み込む。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace FontConverter2ForHollowKnight
{
    class Program
    {
        static void Main(string[] args)
        {

            int counter = 0;

            List<int> id = new List<int>();
            List<float> x = new List<float>();
            List<float> y = new List<float>();
            List<float> width = new List<float>();
            List<float> height = new List<float>();
            List<float> xoffset = new List<float>();
            List<float> yoffset = new List<float>();
            List<float> xadvance = new List<float>();

            string line;

            StreamReader file = new StreamReader("test.fnt");
            while ((line = file.ReadLine()) != null)
            {
                Console.WriteLine(line);
                counter++;
            }

        }
    }
}

f:id:kengo700:20170330171735p:plain

ここから座標の数値を抜き出すには…

正規表現ん…ですかねえ…

本当に、正規表現ほど頭が痛くなるものもなかなかない。使いこなせれば便利なんだろうけど、使いこなせるようになるほど頻繁には必要にならない。 まあ、私の頭が理解して覚えられないだけなんですけどね…

とりあえずidの数値だけ抜き出し。

            StreamReader file = new StreamReader("test.fnt");
            while ((line = file.ReadLine()) != null)
            {
                Console.WriteLine(line);

                Regex re = new Regex(@"id=(?<id>\d+)");

                for (Match m = re.Match(line); m.Success; m = m.NextMatch())
                {
                    string test = m.Groups["id"].Value;
                    Console.WriteLine(test);

                }
                counter++;
            }

f:id:kengo700:20170330174321p:plain

他の数値も抜き出し。

            StreamReader file = new StreamReader("test.fnt");
            while ((line = file.ReadLine()) != null)
            {
                Console.WriteLine(line);

                Regex re = new Regex(@"id=(?<id>\d+).*x=(?<x>[-]?\d+).*y=(?<y>[-]?\d+).*width=(?<width>[-]?\d+).*height=(?<height>[-]?\d+).*xoffset=(?<xoffset>[-]?\d+).*yoffset=(?<yoffset>[-]?\d+).*xadvance=(?<xadvance>[-]?\d+).*page=(?<page>\d+).*chnl=(?<chnl>\d+)");

                for (Match m = re.Match(line); m.Success; m = m.NextMatch())
                {

                    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}",
                        m.Groups["id"].Value,
                        m.Groups["x"].Value, 
                        m.Groups["y"].Value,
                        m.Groups["width"].Value,
                        m.Groups["height"].Value,
                        m.Groups["xoffset"].Value,
                        m.Groups["yoffset"].Value,
                        m.Groups["xadvance"].Value,
                        m.Groups["page"].Value,
                        m.Groups["chnl"].Value);

                }

                counter++;
            }

f:id:kengo700:20170330175958p:plain

バイナリに変換して出力。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

namespace FontConverter2ForHollowKnight
{
    class Program
    {
        static void Main(string[] args)
        {

            int count = 0;

            List<int> id = new List<int>();
            List<float> x = new List<float>();
            List<float> y = new List<float>();
            List<float> width = new List<float>();
            List<float> height = new List<float>();
            List<float> xoffset = new List<float>();
            List<float> yoffset = new List<float>();
            List<float> xadvance = new List<float>();

            string line;

            StreamReader file = new StreamReader("test.fnt");

            // 1~3行目を読み飛ばす
            line = file.ReadLine();
            line = file.ReadLine();
            line = file.ReadLine();

            line = file.ReadLine();

            Regex reg1 = new Regex(@"count=(?<count>\d+)");
            for (Match m = reg1.Match(line); m.Success; m = m.NextMatch())
            {
                Console.WriteLine("{0}", m.Groups["count"].Value);
                count = int.Parse(m.Groups["count"].Value);
            }


            while ((line = file.ReadLine()) != null)
            {

                Regex reg2 = new Regex(@"id=(?<id>\d+).*x=(?<x>[-]?\d+).*y=(?<y>[-]?\d+).*width=(?<width>[-]?\d+).*height=(?<height>[-]?\d+).*xoffset=(?<xoffset>[-]?\d+).*yoffset=(?<yoffset>[-]?\d+).*xadvance=(?<xadvance>[-]?\d+).*page=(?<page>\d+).*chnl=(?<chnl>\d+)");

                for (Match m = reg2.Match(line); m.Success; m = m.NextMatch())
                {

                    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}",
                        m.Groups["id"].Value,
                        m.Groups["x"].Value, 
                        m.Groups["y"].Value,
                        m.Groups["width"].Value,
                        m.Groups["height"].Value,
                        m.Groups["xoffset"].Value,
                        m.Groups["yoffset"].Value,
                        m.Groups["xadvance"].Value,
                        m.Groups["page"].Value,
                        m.Groups["chnl"].Value);

                    id.Add(int.Parse(m.Groups["id"].Value));
                    x.Add(float.Parse(m.Groups["x"].Value));
                    y.Add(float.Parse(m.Groups["y"].Value));
                    width.Add(float.Parse(m.Groups["width"].Value));
                    height.Add(float.Parse(m.Groups["height"].Value));
                    xoffset.Add(float.Parse(m.Groups["xoffset"].Value));
                    yoffset.Add(float.Parse(m.Groups["yoffset"].Value));
                    xadvance.Add(float.Parse(m.Groups["xadvance"].Value));
                }
                
            }


            float pagechnl = 1;

            FileStream fs = null;

            // ファイルを新規作成(既に存在する場合は上書き)
            fs = new FileStream(@"testnew.bin", FileMode.Create);

            byte[] data_count = BitConverter.GetBytes(count);
            fs.Write(data_count, 0, data_count.Length);


            for (int i = 0; i < count; i++)
            {
                yoffset[i] = yoffset[i] * (float)-1.0;
            }

            for (int i = 0; i < count; i++)
            {
                Console.WriteLine("{0}", i);

                byte[] data_id = BitConverter.GetBytes(id[i]);
                fs.Write(data_id, 0, data_id.Length);

                byte[] data_x = BitConverter.GetBytes(x[i]);
                fs.Write(data_x, 0, data_x.Length);

                byte[] data_y = BitConverter.GetBytes(y[i]);
                fs.Write(data_y, 0, data_y.Length);

                byte[] data_width = BitConverter.GetBytes(width[i]);
                fs.Write(data_width, 0, data_width.Length);

                byte[] data_height = BitConverter.GetBytes(height[i]);
                fs.Write(data_height, 0, data_height.Length);

                byte[] data_xoffset = BitConverter.GetBytes(xoffset[i]);
                fs.Write(data_xoffset, 0, data_xoffset.Length);

                byte[] data_yoffset = BitConverter.GetBytes(yoffset[i]);
                fs.Write(data_yoffset, 0, data_yoffset.Length);

                byte[] data_xadvance = BitConverter.GetBytes(xadvance[i]);
                fs.Write(data_xadvance, 0, data_xadvance.Length);

                byte[] data_pagechnl = BitConverter.GetBytes(pagechnl);
                fs.Write(data_pagechnl, 0, data_pagechnl.Length);

            }


            fs.Close();


        }
    }
}

f:id:kengo700:20170330181444p:plain

距離フィールドテクスチャをdds形式に変換する。以前にGIMPとGMIPのddsプラグインを入れていたので、それを使ってみる。

画像を開き、「名前を付けてエクスポート」からdds形式で保存する。

f:id:kengo700:20170330181718p:plain

f:id:kengo700:20170330181701p:plain

f:id:kengo700:20170330181942p:plain

resources0003.-5の座標データと文字数と画像サイズ部分を書き換えてリパック。

f:id:kengo700:20170330182543p:plain

f:id:kengo700:20170330184000p:plain

f:id:kengo700:20170330184023p:plain

いい感じに黒縁が出てくれた! 「消えゆく町」部分はフォントに入っていないので、別のフォントが表示されている。

「ダ」や「ト」の斜め部分が少しガタガタしているのは、フォント作成時の調整が必要か? 行間の調整も必要かもしれない。

しかし前述の通り、2048x2048に日本語の文字を全て詰め込むのは難しいので、距離フィールドバージョンのビットマップフォントの作成は、校正が一通り終わってから行おうと思う。とりあえずそれまでは、これまでのBitmap font generatorで作成したフォントを使用することにする。

エリアタイトル用のフォント以外の、会話文とかのフォントも距離フィールドフォントに変えたほうがきれいに表示されるかもしれないけど、面倒くさいのでとりあえずそのままにする予定。時間と気力があったら作るかもしれない。校正作業が終わってから考える。

差分パッチの作成方法の調査

イメージとしては中文化MODのように、実行ファイルをダブルクリックすれば日本語化が完了するようなものを作りたい。

しかしちょっと調べた範囲では、難しそう。

差分パッチを適応するために専用のソフトが必要になってしまうと意味がない。

差分パッチを作るのは一旦諦めよう。

差分パッチの作成

4/1~

翻訳に疲れたので、コメントで教えていただいた差分パッチ作成プログラムを試してみる。

このソフトの名前でググったら、似たようなソフトは幾つかあるらしい。前回は自分の調べ方が不十分だった。

ダミーのファイルでソフトの使い方を確かめたりしつつ、最終的に下記のような設定に。

f:id:kengo700:20170401211712p:plain

ちなみにゲームのアイコンの場所は、下記ページの方法で分かる。

f:id:kengo700:20170401211836p:plain

旧バージョンのフォルダは、ゲームのフォルダそのままに。あらかじめSteamクライアントの「プロパティ」->「ゲームファイルの整合性を確認」で英語版に戻しておく。

新バージョンのフォルダは、現在手動で日本語化ファイルを作っているフォルダを指定。「UnityEX.exe」とか余計なファイルも入っているが、「旧フォルダに無く…」「旧フォルダに有り…」のチェックを外していれば問題ないはず。

f:id:kengo700:20170401212844p:plain

差分パッチ実行時のフォルダは「フォルダ直接指定」でゲームのフォルダを指定。一番下のチェックを入れておくことで、ゲームのインストール先を変えている人は手動で設定できるように。

「差分ファイルのビルドを開始」で差分パッチを作成。

f:id:kengo700:20170401212356p:plain

作成できた。

f:id:kengo700:20170401213541p:plain

ちなみに表示を変えるとアイコンが変わってしまう。ちょっと気持ち悪いけど、まあいいか。

f:id:kengo700:20170401213614p:plain

差分パッチの容量は43.3MB。resources.assetsとsharedassets0.assetsあわせて427MBなので、だいぶ容量の削減にもなってる。

パッチを実行してみる。

f:id:kengo700:20170401213832p:plain

f:id:kengo700:20170401213858p:plain

f:id:kengo700:20170401213931p:plain

ちゃんとresources.assetsとsharedassets0.assetsが更新された。ちなみにファイルの更新日時は、パッチを実行した日時ではなく、日本語版のresources.assetsとsharedassets0.assetsを作成した日時になっている。

f:id:kengo700:20170401214034p:plain

f:id:kengo700:20170401215810p:plain

ちなみに、エリアタイトルのフォントを飴鞭ゴシックに変えたのに合わせて、タイトルやメニューのフォントも飴鞭ゴシックに変更した。ちょっと癖が強すぎる気もするけど、慣れれば気にならなくなるはず。のちのち元に戻すかもしれない。

  • TrajanPro-Bold.ttf(←飴鞭ゴシックのamemuchigothicmono-02.ttfをリネーム)
  • TrajanPro-Regular.ttf(←飴鞭ゴシックのamemuchigothicmono-02.ttfをリネーム)

ファイルを間違えた!

f:id:kengo700:20170401215820p:plain

というか飴鞭のビットマップフォントを上書きしてしまってる! ミスった…

日本語フォントの改善3

また同じビットマップフォントを作り直すのも嫌なので、距離フィールドバージョンを、文字サイズを小さくして作ってみる。

サイズを18にすれば、2048x2048でも1枚に収まった。

f:id:kengo700:20170401220924p:plain

f:id:kengo700:20170401221430p:plain

f:id:kengo700:20170401222706p:plain

文字の隙間が足りなくて見切れてしまっている。あと作成したフォントの文字の大きさが小さいからか、少しつぶれてしまっている。 サイズを小さくして作るのは良くなさそう。

諦めてビットマップフォントを作り直して、距離フィールドバージョンは校正が終わってから作ろう。

作り直した。

f:id:kengo700:20170401224820p:plain

差分パッチの作成2

配布するパッチを作成する。

作成した。

f:id:kengo700:20170401231816p:plain

手動で日本語化する方も更新。

f:id:kengo700:20170401231858p:plain

f:id:kengo700:20170401232819p:plain

前に配布したファイル名もちょっと修正。下訳中のバージョンが0.0.x、校正中のバージョンが0.x.x、一通り校正が終わったバージョンがx.x.x。

翻訳作業3

4/2~

ラスボス撃破! やったぜ! 成し遂げたぜ!

…え? 闘技場? スピードラン? 達成度100%? スチールソウル?

…さて、翻訳作業に戻りますかネ…

4/4~

翻訳の進捗がやっと100%に! 長かった… そして最後の数テキストは無理矢理訳した感がぱない…

f:id:kengo700:20170404213052p:plain

何度か見直して最低限の見直しをしてから、日本語化パッチを作ろう。

日本語化パッチの作成

量が多すぎるので見直しは断念。日本語化パッチを作ってしまおう。

とりあえず日本語化してみる。少しプレイした限りでは問題はなさそう。翻訳のクオリティは、これから上げて行かないといけないけど…

f:id:kengo700:20170404221611p:plain

英語版に戻して、差分パッチを作る。

作った。

f:id:kengo700:20170404222124p:plain

合わせて作業所も書き換え。

f:id:kengo700:20170404223250p:plain

校正作業

作業に取り掛かる前にWikiを修正する。

修正した。

f:id:kengo700:20170404223837p:plain

さて、後はゲームを最初からプレイしながら、違和感のあるテキストをしらみつぶしに直していくだけだ…

ついでにWikiの攻略情報を書くために、スクショを取りまくる。

さらにSteamのスレッドとかを読み漁って、物語の背景をちゃんと把握する必要もある。
 

ここからが本当の有志翻訳だ!!

…心が折れそうだ
 

とりあえずSteamのスレッドから考察が書かれているっぽいものをメモ。あとでちゃんと読む。

4/6~

上から順に、誤字や口調の揺れを修正。

4/7~

とりあえずID+ファイル名ソートで「BELIEVE_MOTH」まで進んだ。

飽きたのでゲームをプレイしながら修正することに。ついでにスクショを取って、Wikiも編集する。

フォントの抜けを見つけた。「~」「瘦」が表示されていない。

f:id:kengo700:20170408125109p:plain

4/8~

「~」も「瘦」も、フォントを作るときに使った文字データのファイル「SHIFTJIS_custom_win_bom_utf8」に入っていなかった。

「瘦」は「痩」に書き換えた。 「~」は追加してフォントをあとで作り直そう(忘れそう…)。

Wikiのチャーム一覧を使いやすく、でもネタバレに配慮しようとした結果、図を白黒にしてみた。

f:id:kengo700:20170408144825p:plain

ネタバレ防止になってない気がするけど、逆にカッコいい! しかしそもそも、攻略Wikiでネタバレに配慮する必要はあるのだろうか…

まあ、結果的にゲームを楽しめなくなる可能性は減らしておいたほうがいいじゃろ。

ちなみにWikiの図は、ペイントとパワポで手作業で作ってる。いい加減、他のイラスト編集ソフトを覚えないとなぁ…

f:id:kengo700:20170408145153p:plain

4/11~

ID+ファイル名ソートで「MASK_MAKER_UNMASK」まで修正。けっこう進んだと思ったけど、まだ半分だった。

すべてゲームをプレイしてチェックするのは時間がかかりすぎるので、ある程度校正作業が進んだバージョンを完成版として、その後も少しずつ校正を進めていこう。

特にドリームネイルのセリフチェックがしんどすぎる…

とりあえず現状の状態でパッチを更新する(ホントはもう少し頻繁に更新するつもりだったけど、余裕がなかった)。

日本語フォントの改善4

抜けが見つかった「~」を追加する。せっかくなので花園明朝の代わりに、先日公開された「Noto Serif CJK」を使ってみる。

f:id:kengo700:20170411201939p:plain

f:id:kengo700:20170411202039p:plain

「ARIAL SDF Atlas.tex」「perpetua_tmpro Atlas.tex」「Perpetua-SDF Atlas.tex」「TrajanPro-Bold SDF Atlas.tex」を「Noto Serif CJK」バージョンに書き換え。

さらに「NotoSerifCJKjp-Regular.otf」を「WOFFコンバータ」でttf形式に変換し、「Perpetua.ttf」に上書き。

試そうとしたらアプデが入った。

アプデ対応

v1.0.2.8へのアップデートが来たので確認。

テキストについては修正18、追加13。

f:id:kengo700:20170411211426p:plain

作業所のデータを手動で修正。

次にフォントのデータを確認。

上がv1.0.1.4、下がv1.0.2.8。フォントデータも作り替えられている模様。フランス語が追加されたからか?

f:id:kengo700:20170411215215p:plain

f:id:kengo700:20170411215614p:plain

f:id:kengo700:20170411215657p:plain

f:id:kengo700:20170411215739p:plain

f:id:kengo700:20170411215813p:plain

試しに「resources_00001.-5」などはv1.0.1.4用に作ったものから変えずに、v1.0.2.8の「resources.assets」と「sharedassets0.assets」にリパックして試してみる。

f:id:kengo700:20170411221443p:plain

f:id:kengo700:20170411221456p:plain

メニューなどは問題なかったが、ビットマップフォントの部分は文字が表示されなかった。 やっぱり、そう簡単にはいかないか…

飴鞭ゴシックの部分も表示されていないので、「Noto Serif CJK」のビットマップフォントの作成にミスったわけではないはず。

v1.0.1.4とv1.0.2.8のファイルの比較結果を眺めてみて、なんとなく怪しく感じた各ファイルの0x00000014部分を12から15に書き換えてみる(フォント名の情報より前の部分なので、何かしら重要な情報なはず。逆にフォント名から座標情報までの部分は、文字数や画像のサイズ以外の部分は、フォントのサイズなどの情報なはずなので、間違えても文字が表示されなくなることはないと思われる)。

f:id:kengo700:20170411224540p:plain

結果、ちゃんと表示された。

f:id:kengo700:20170411224512p:plain

f:id:kengo700:20170411224628p:plain

f:id:kengo700:20170411224639p:plain

花園明朝AとNoto Serif CJKでは、正直違いはほとんど分からない。Noto Serif CJKで進めてみる。

f:id:kengo700:20170411231851p:plain

オブジェクトを調べるコマンドの文字が、また少しだけ下がってしまっている。そのうち直す。

f:id:kengo700:20170411235052p:plain

「~」の文字もちゃんと表示された。

日本語化パッチの更新

現状の状態でパッチを更新する。念のため校正箇所をざっとチェックする。

修正箇所は451。

f:id:kengo700:20170411234158p:plain

修正したフォントと校正中の翻訳データを使って、日本語化テストパッチを作成する。

f:id:kengo700:20170412000407p:plain

日本語化テストパッチでリパックした「resources.assets」と「sharedassets0.assets」でゲームを起動してみる。問題なさげ。

ゲームを英語版に戻し、「udm差分ファイル作成ツール」で差分パッチを作成。

f:id:kengo700:20170412000948p:plain

差分パッチで日本語化してゲームを起動してみる。問題なさげ。

それぞれ圧縮し、Googleドライブにアップロードし、公開。

作業所で告知。

f:id:kengo700:20170412001952p:plain

そういえば「日本語化パッチ」と「日本語化テストパッチ」の説明を書き忘れていたので追記。

f:id:kengo700:20170412002203p:plain

校正作業2

4/13~

ID+ファイル名ソートで最後まで修正した。ぱっと見で違和感のある訳と、誤訳や誤字脱字は結構減らせたはず。

実際にゲーム画面で見ると違和感がある訳については、地道にプレイしながらつぶしていくしかない。

しかしそれだと何週間もかかってしまうので、そろそろ完成版として告知するべきか…

とりあえず、大型アプデで楽しくなった「Galactic Junk League」をプレイしながら考えよう…

4/15~

Steamガイドに地図データがアップロードされていた。エリアごとに別レイヤーにしてくれている。

Steam Community :: Guide :: Ultimate Map Template for all your mapping needs

翻訳とは直接関係ないけど、Wikiの編集が捗りそう。

f:id:kengo700:20170415135646p:plain

日本語フォントの改善5

距離フィールドバージョンのフォントを作成する。

先ずは翻訳データのtsvファイルから、文字部分を抽出し、重複文字を除いたテキストファイルを出力するプログラムを作る。

もちろん飴鞭ゴシックが表示されるエリア名などの文字データだけ抽出すればフォントサイズは圧縮できるものの、抽出が面倒くさいし、今後校正したときに文字が抜けてしまうのを防ぐため、多めに入れておく。

重複削除のプログラムは下記ページを参考にさせていただいた。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace ConvertTextForHollowKnight
{
    class Program
    {
        static void Main(string[] args)
        {

            // 引数チェック
            if (args.Length >= 2)
            {

                string translatedFile = args[0];
                string outputFile = args[1];


                List<string> translated_text_jp = new List<string>();

                // 翻訳データファイル(tsv形式)をオープン
                if (File.Exists(translatedFile))
                {

                    using (StreamReader reader_translated = new StreamReader(translatedFile, Encoding.UTF8))
                    {
                        // 1行目(見出し行)はスキップ
                        reader_translated.ReadLine();


                        // ファイル末尾まで、1行ずつ読み込み
                        while (reader_translated.EndOfStream == false)
                        {
                            string line = reader_translated.ReadLine();

                            char[] delimiterChars = { '\t' };
                            string[] words = line.Split(delimiterChars);

                            string temp_file = "";
                            string temp_id = "";
                            string temp_text_en = "";
                            string temp_text_jp = "";

                            int count = 0;
                            foreach (string word in words)
                            {
                                switch (count)
                                {
                                    case 0:
                                        temp_file = word;
                                        break;
                                    case 1:
                                        temp_id = word;
                                        break;
                                    case 2:
                                        temp_text_en = word;
                                        break;
                                    case 3:
                                        temp_text_jp = word;
                                        break;
                                }
                                count++;
                            }

                            Console.WriteLine("{0},{1}", temp_file, temp_id);

                            translated_text_jp.Add(temp_text_jp);

                        }

                        string temp = "~ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz}{|~ 、。,.・:;?!゛゜´`¨^ ̄_ヽヾゝゞ〃仝々〆〇ー―‐/\\?∥|…‥‘’“”()〔〕[]{}〈〉《》「」『』【】+?±×÷=≠<>≦≧∞∴♂♀°′″℃¥$¢£%#&*@§☆★○●◎◇◆□■△▲▽▼※〒→←↑↓〓∈∋⊆⊇⊂⊃∪∩∧∨¬⇒⇔∀∃∠⊥⌒∂∇≡≒≪≫√∽∝∵∫∬ʼn♯♭♪†‡¶◯0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψωАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя─│┌┐┘└├┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠┯┨┷┿┝┰┥┸╂";

                        foreach (string text in translated_text_jp)
                        {
                            temp += text;
                        }



                        StreamWriter writer = new StreamWriter(outputFile, true);

                        writer.Write(temp.ToCharArray().Distinct().ToArray());

                        writer.Close();


                    }
                }
            }

        }
    }
}

プログラムの引数にtsvファイルの名前と出力ファイルの名前を指定して実行すると、下記のようなテキストファイルが出力される。 問題ないっぽい。

f:id:kengo700:20170415151906p:plain

「Hiero」のSample Text欄に文字をコピペ。フォントの設定を調整。サイズ36で2048x2048の画像に収まってくれた。

f:id:kengo700:20170415152234p:plain

フォントを出力し、座標データの方は自作のプログラムでバイナリに変換、画像の方はGIMPでdds形式に変換

f:id:kengo700:20170415152733p:plain

resources0003.-5の座標データと文字数と画像サイズ部分を書き換えてリパック。

f:id:kengo700:20170415153619p:plain

f:id:kengo700:20170415154637p:plain

f:id:kengo700:20170415154700p:plain

それなりにいい感じだけど、フォントの端っこに他の文字の端が入り込んでしまっている。作り直しだ。

作り直した。

f:id:kengo700:20170415160336p:plain

f:id:kengo700:20170415160404p:plain

f:id:kengo700:20170415160427p:plain

おーけー。文字サイズも、これくらいがちょうどいいかもしれない。

設定をちょっとミスってたので修正。距離フィールド生成のScaleパラメータを1にしてしまっていたので10に変更。少しだけフォントのクオリティが上がるらしい。

f:id:kengo700:20170415164917p:plain

f:id:kengo700:20170415170135p:plain

f:id:kengo700:20170415170153p:plain

少しだけきれいになった。

フォントについて他に気になることは、オブジェクトを調べるときの文字が少しだけ下にずれていることと、会話文とかのフォントも距離フィールド版にしたほうがいいかもしれないということくらいか。

どちらも些細なことなので、フォント作成については一旦完了ということにしてしまおう。

校正作業3

テストプレイで真菌ちほーを攻略していたら、エリアタイトルが正しく表示されなかった

f:id:kengo700:20170415215729p:plain

調べてみたらフォントのミスではなく、翻訳が抜けていた。

f:id:kengo700:20170415215804p:plain

「FUNGUS_SUPER」と「FUNGUS_MAIN」の間に「FUNGUS_SHAMAN」が入っていたので見落としていた。修正。

4/16~

表示がちょっとだけ微妙なところがちょいちょいあるけど、直すのが大変すぎるのでこのままでいいかな… デバッグモードが使えるならまだしも、表示を確かめるためだけに毎回最初から数時間プレイするのはしんどすぎる。

↓アイテムやスキルの「取得」とか「吸収」の文字が少し近すぎる。
[(ネタバレ注意! クリックで表示)]

f:id:kengo700:20170416162630p:plain

↓白背景に黒文字だと、少しだけ他の文字の端が見えてしまっている(「イ」の上とか)。
[(ネタバレ注意! クリックで表示)]

f:id:kengo700:20170416162932p:plain

↓ラスボスの表記が改行してしまってるのは、さすがに直すべきだな…
[(ネタバレ注意! クリックで表示)]

f:id:kengo700:20170416163107p:plain

↓少しだけ改名してもらった
[(ネタバレ注意! クリックで表示)]

f:id:kengo700:20170416165518p:plain

よし! 一旦完成版としてしまおう!

こだわりすぎると、どれだけ時間かけても校正作業が終わらない!

日本語化パッチの作成2

前回からの変更部分をざっとチェック。→修正箇所は321。

リパックして動作確認。

f:id:kengo700:20170416173110p:plain

f:id:kengo700:20170416173134p:plain

f:id:kengo700:20170416173204p:plain

問題なさげ。

差分パッチを作成。動作確認。

問題なさげ。

作業所で公開。

f:id:kengo700:20170416175142p:plain

f:id:kengo700:20170416175525p:plain

Googleアカウントからログアウトして、ダウンロードできることを確認。

Wikiのページを更新。

f:id:kengo700:20170416181949p:plain

Twitterで告知。

f:id:kengo700:20170416183505p:plain

これでやっとひと段落かな…

4/17~

ブログやWikiのコメントで、Mac版やGOG版についての質問があったので、取り急ぎWikiに追記。

f:id:kengo700:20170417205029p:plain

f:id:kengo700:20170417205103p:plain

正直に言って他の環境のことは全然考えていなかった…

とは言っても、有志翻訳のパッチのチェックのためだけにMacを買うわけにもいかない…

Linuxなら仮想環境で確認できるけど、日本でLinuxでSteamのゲームをプレイしている人はいるのだろうか…

日本語化パッチの不具合修正

4/23~

下記記事のコメントで日本語化パッチの不具合の情報をいただいたので修正する。

追加エンディングの要素達成すると、ラスボス倒した後、 追加エンディングのアニメが出て来る前に、エラーが発生します

「追加のエンディングの要素」というのは、おそらく「マッシュルームのクエスト」のことか。

エンドコンテンツの最後にエラーを起こしてしまうとか、マジで申し訳ないです。本当に…
 

クエストを終わらせて更にラスボスを撃破するのは、時間がかかってしまうので、セーブデータを流用できないか調べる。

ググってみると、Hollow Knightのセーブデータは下記にあるらしい。

  • C:\Users\「ユーザー名」\AppData\LocalLow\Team Cherry\Hollow Knight

あった。

f:id:kengo700:20170423141741p:plain

ゲームを英語版に戻し、試しに「user4.dat」を「user3.dat」に変えてゲームを起動してみる。 → 変化なし。

試しに「user4.dat」を削除してゲームを起動してみる。 → 変化なし。

???

「user4.dat」を別フォルダにコピーし、ゲームを少し進めて最初の温泉のベンチでセーブ、別フォルダにコピーした「user4.dat」を上書きしてみる。 → データが巻き戻った。

良く分からんけど、セーブデータを直接削除したり順番を変えたりはできないが、別のセーブデータを上書きするのはオッケーなのかな?
 

とにかく、エラー情報をいただいた方にコメントでセーブデータの提供を依頼。

同時にWikiとツイッターで不具合の周知。

f:id:kengo700:20170423142954p:plain

f:id:kengo700:20170423143033p:plain
 

念のためノーマルエンドのエンディングも調べてみる。

→ エラー終了が起きた。マジか…

f:id:kengo700:20170423142513p:plain

他の条件を調べる。

  • ノーマルエンド2 → エンドロールの最後でクラッシュ
  • メニューからクレジットを見ると → 最後でクラッシュ
  • 英語版にもどしてノーマルエンド2 → 問題なし
  • 英語版でメニューからクレジット → 問題なし

タイミング的にキックスターターの支援者の表示の箇所。

f:id:kengo700:20170423142704p:plain

「&」があやしい。

翻訳データをコンバートした「EN_Credits List.txt」を確認。

f:id:kengo700:20170423143736p:plain

なんか、変なことになってる…

ああ…なるほど… <br>&&lt;br&gt;&&amp;lt;br&amp;gt;&amp;

…酷いミスだ…

他の部分も全部そうなっとる…

f:id:kengo700:20170423144238p:plain

これでもちゃんと動いていたのは、ゲーム側の会話部分の表示処理がうまいこと変換してくれてたのかな。エンドロール部分は処理が違うからエラー終了したと…

処理の順番を変えればいけるかな。

                            temp_text_jp = temp_text_jp.Replace("&", "&amp;");
                            temp_text_jp = temp_text_jp.Replace("'", "&#39;");
                            temp_text_jp = temp_text_jp.Replace("<", "&lt;");
                            temp_text_jp = temp_text_jp.Replace(">", "&gt;");
                            temp_text_jp = temp_text_jp.Replace("\"", "&quot;");

f:id:kengo700:20170423150646p:plain

f:id:kengo700:20170423150519p:plain

ダメか…

素直にエラーログを見るか。

4/24~

ログを見ても良く分からん。

↓error.log

Read from location 01243000 caused an access violation.

どこかの読み込みでアクセス違反が起きている?

↓output_log.txt

NullReferenceException: Object reference not set to an instance of an object
  at TMPro.TMP_FontAsset.ReadFontDefinition () [0x00000] in <filename unknown>:0 

フォントの定義の読み込み関数で例外?

良く分からないので、色々試してみることにする。

まずはエンドロールで使われているフォントを特定する。

英語版でフォントの座標データのサイズ部分を書き換えてリパックし、ゲーム内での表示を見る。

f:id:kengo700:20170424204336p:plain

結果、エンドロールで使われているフォントのファイルは「resources_00002.-5」と分かった。 以前調べた結果では、これは会話文など多くの部分で使われているフォント。

ますますエンドロールの最後だけでエラー終了する意味が分からない…

英語版と、日本語化用のフォントの最新版を比較。

f:id:kengo700:20170424204623p:plain

なぜか「0x000044」の部分が食い違っているので修正してみる。 → エラー直らず。

f:id:kengo700:20170424204849p:plain

「0x03DE58」の部分も違っているので修正してみる。→ エラー終了せずに表示された!

f:id:kengo700:20170424204941p:plain

つまりv1.0.2.8へのアップデート時に、フォントの修正を横着していたのが原因か…

他のファイルも比較する。

↓「resources_00003.-5」の「0x000048」部分を修正
f:id:kengo700:20170424211220p:plain

↓「sharedassets0_00001.-1」の「0x000044」~の部分を修正
f:id:kengo700:20170424211334p:plain

リパックして動作チェック。

オープニングから最初の温泉までと、最後のベンチからノーマルエンド2の最後まで確認。問題なさげ。

差分パッチを作成して動作チェック。

フランス語の文字が抜けていることに気付いたけど、次回直します。あんまりいっぺんに変えると、別のバグが入りそう…

f:id:kengo700:20170424215231p:plain

パッチを作業所とWikiで公開。Wikiのページを更新。Twitterで周知。

f:id:kengo700:20170424220616p:plain

f:id:kengo700:20170424220556p:plain

これで「追加エンディング」のエラー終了も直っていて欲しいけれど、それをちゃんと確かめなくては。

確かめるためには、追加エンディングの要素を達成し、前バージョンのパッチでラスボスを倒してエラーを確認し、新バージョンのパッチでラスボスを倒してエラーが起きないことを確かめなければならぬ…

あのラスボスを、そんなぽんぽん倒せるほどのプレイヤースキルはないぞ…

まあ頑張ろう…

日本語化パッチの不具合修正2

4/27~

パッチの不具合をご報告いただいた方からセーブデータをいただいたので、ラスボスに再挑戦。

いただいた「user2.dat」と「user2.dat.bak」を下記フォルダにコピー(自分のセーブデータは事前にバックアップ)。

C:\Users\「ユーザー名」\AppData\LocalLow\Team Cherry\Hollow Knight

そして念のため、一度英語版に戻し、最新の日本語化パッチを当てておく。

おー、ちゃんとセーブデータが移行できて… 26時間で達成度100%?! ぱねぇ…

f:id:kengo700:20170427212508p:plain

挑戦3回目でラスボス撃破。

ちゃんと最後までエラー終了しないことを確認。

前のバージョンのパッチでエラー終了するのは、確かめなくて大丈夫かな。

トゥルーエンド2は動画でしか見たことがなかったので勘違いしていたが、「追加ムービーはエンドロールの後に表示された」ので、起きた症状は先日確認したものと同じっぽい。

不具合修正完了!

校正作業3(途中)

引き続きゲームをプレイしつつ、Wikiを編集しながら、校正作業を進める。

アプデ対応

5/18

v1.0.3.1へのアップデートが来たので確認。

テキストについては修正7、追加3、削除31。

ゲームに使われていなかったテキストが削除されたっぽい? 卵のテキストが無くなっていた(下図)が、ミスかもしれないのでこれだけ元のままにしておく。 追加は「White Defender」。

f:id:kengo700:20170518205948p:plain

作業所のデータを手動で修正完了。

次にフォントデータを確認。

上がv1.0.2.8、下がv1.0.3.1。変更箇所が少なそうで助かる。

f:id:kengo700:20170518210551p:plain

f:id:kengo700:20170518210640p:plain

f:id:kengo700:20170518210703p:plain

f:id:kengo700:20170518210743p:plain

f:id:kengo700:20170518210807p:plain

f:id:kengo700:20170518210839p:plain

f:id:kengo700:20170518210904p:plain

日本語化テストパッチの座標データの変更箇所を手動で修正。リパック。

上書きしてゲーム起動。→起動しない。

最初のロゴも表示されず、真っ黒な画面が続くだけ。

…これは大変そうだ
 

念のため修正した座標データファイルを再確認。修正ミスはなさそう。

「ゲームの整合性を確認」で英語版に戻し、最新の英語版の「resources.assets」「sharedassets0.assets」に対してリパック。

さっきは出なかった「TrajanPro-Bold.ttfはありません」「TrajanPro-Regular.ttfはありません」のUnityEXのエラーが出た。 さっきはリパックする「resources.assets」「sharedassets0.assets」を間違えていたかもしれない。

エラーは無視してリパックし、上書きしてゲーム起動。→起動した。

f:id:kengo700:20170518221353p:plain

メニューの文字が飴鞭ゴシックではなくなっている。「TrajanPro-Bold.ttf」と「TrajanPro-Regular.ttf」がリパックされていないためか。

アンパックしたファイルを確認してみると、resourcesフォルダに「TrajanPro-Bold.ttf」と「TrajanPro-Regular.ttf」が無く、sharedassets0フォルダに移動していた。 同じようにパッチのファイルを移動し、再びリパック。

上書きしてゲーム起動。メニューのフォントが飴鞭ゴシックになっていることを確認。

f:id:kengo700:20170518223750p:plain

念のためゲームスタートから最初のダンジョンまでと、ノーマルエンドの最後までプレイして問題ないことを確認。

ゲームを英語版に戻し、差分パッチを作成。手動パッチも更新。

差分パッチを実行し、日本語化したゲームを起動できることを確認。

圧縮してGoogleドライブにアップロードし、共有URLを作業所に貼り付け。Googleアカウントからログアウトし、ダウンロードできることを確認。

Wikiを編集。

現在のSteamはアプデをオフにはできないので、全員最新バージョンでプレイするはず。なので日本語化パッチ_v1.0.0の不具合の告知はコメントアウトしておこう。

アプデ対応

6/23

v1.0.3.4へのアップデートが来たので確認。

最新版のassetsファイルをアンパックしてみたところ、アレなアレを発見。

f:id:kengo700:20170623002913p:plain

とりあえず正式版を誤って混入してしまわないように、取り急ぎ作業所に保護を設定。

f:id:kengo700:20170623003213p:plain

アレなアレは見なかったことにして、正式実装までは日本語化MODの更新は続けることにしよう。

テキストを抽出して比較すると、700行以上変更されていることを確認。

f:id:kengo700:20170623004028p:plain

しかしざっと見たところ、単純に順番が入れ替わっている行も多そう。 プログラムを組んで処理するべき量かもしれないが、新しく書いたプログラムにバグがないかどうか確かめるのも面倒だし、58,000行のDivinity: Original Sin 2の有志翻訳を進めていると、700行ぐらい手作業で何とかなんじゃね? と思ってしまう(錯乱)。

昔書いた翻訳済みテキストの挿入プログラムを見ると、「Dictionary」を使って「entry name」を検索して挿入しているので、作業場側の順番は「EN_*.txt」ファイルと一致してなくてもいいっぽい(昔書いたプログラムなので、自身は無い…)。 なので順番は気にせず、修正or追加or削除されたテキストだけチェックして修正すればOKか?

とりあえず作業所のバックアップコピーを取って、修正作業を進めてみる。

WinMerge上で変化がある行を、コピペしてもう片方のファイルで検索して、存在するか確認するのを永遠繰り返す。 単純作業だけど、単純作業だからこそしんどいし、単純ミスも起きそう。 素直にチェックプログラムを書こう。

やっつけプログラムだけど、たぶん問題なかろ…

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace CompareTextForHollowKnight
{
    class Program
    {
        static void Main(string[] args)
        {

            string oldFile = @"C:\Temp\hollowknight\en_v1.0.3.1\hollowknight_en.tsv";
            string newFile = @"C:\Temp\hollowknight\en_v1.0.3.4\hollowknight_en.tsv";

            string outputFile = @"C:\Temp\hollowknight\comparisonResult_v1.0.3.1_v1.0.3.4.tsv";

            // 出力用ファイルを開く
            using (StreamWriter writer = new StreamWriter(outputFile, false))
            {

                writer.Write("[deleted]\n");


                // テキストデータファイルを開く
                using (StreamReader oldtexts = new StreamReader(oldFile, Encoding.UTF8))
                {
                    // 1行目(見出し行)はスキップ
                    oldtexts.ReadLine();


                    // ファイル末尾まで、1行ずつ読み込み
                    while (oldtexts.EndOfStream == false)
                    {

                        string oldtext = oldtexts.ReadLine();

                        bool deleted = true;

                        // 検索
                        using (StreamReader newtexts = new StreamReader(newFile, Encoding.UTF8))
                        {
                            // 1行目(見出し行)はスキップ
                            newtexts.ReadLine();

                            // ファイル末尾まで、1行ずつ読み込み
                            while (newtexts.EndOfStream == false && deleted == true)
                            {

                                string newtext = newtexts.ReadLine();

                                if (oldtext == newtext) deleted = false;

                            }


                        }

                        // 出力
                        if (deleted == true)
                        {
                            writer.Write("{0}\n", oldtext);
                            Console.WriteLine("{0}\n", oldtext);
                        }
                    }


                }

                writer.Write("[added]\n");

                // テキストデータファイルを開く
                using (StreamReader newtexts = new StreamReader(newFile, Encoding.UTF8))
                {
                    // 1行目(見出し行)はスキップ
                    newtexts.ReadLine();


                    // ファイル末尾まで、1行ずつ読み込み
                    while (newtexts.EndOfStream == false)
                    {

                        string newtext = newtexts.ReadLine();

                        bool added = true;

                        // 検索
                        using (StreamReader oldtexts = new StreamReader(oldFile, Encoding.UTF8))
                        {
                            // 1行目(見出し行)はスキップ
                            oldtexts.ReadLine();

                            // ファイル末尾まで、1行ずつ読み込み
                            while (oldtexts.EndOfStream == false && added == true)
                            {

                                string oldtext = oldtexts.ReadLine();

                                if (oldtext == newtext) added = false;

                            }


                        }

                        // 出力
                        if (added == true)
                        {
                            writer.Write("{0}\n", newtext);
                            Console.WriteLine("{0}\n", newtext);
                        }
                    }
                    
                }

            }

        }

    }
}

このプログラムを使って、修正or追加or削除されたテキストを156行まで絞り込めた。これぐらいなら手作業でチェックできる。

f:id:kengo700:20170623013216p:plain

作業所を手動で修正。疲れた…

修正:28、追加:90、削除:8。誤字の修正、クレジットの記載の追加と来月のアプデ内容のテキストの追加。

ちょっとしたネタバレを見てしまったけど、アプデがすごい楽しみにもなった!

次にフォントデータを確認。

上がv1.0.3.1、下がv1.0.3.4。

f:id:kengo700:20170623032601p:plain

f:id:kengo700:20170623032655p:plain

f:id:kengo700:20170623032740p:plain

f:id:kengo700:20170623032815p:plain

f:id:kengo700:20170623032848p:plain

f:id:kengo700:20170623032917p:plain

f:id:kengo700:20170623032951p:plain

f:id:kengo700:20170623033013p:plain

f:id:kengo700:20170623033041p:plain

f:id:kengo700:20170623033105p:plain

これは… 今の寝不足な状態では必ず作業にミスるやーつ。素直に寝よう。

6/23

フォントデータの更新作業

resources_00001.-5の修正、上がv1.0.3.1、中がv1.0.3.4、下が修正した日本語フォント。

f:id:kengo700:20170623200921p:plain

f:id:kengo700:20170623201202p:plain

次のresources_00002.-5を改めて見てみると、フォント名も変わってるっぽい。「HannariMincho」!

f:id:kengo700:20170623202040p:plain

アンパックしたファイルを確認してみると、確かに前には無かったファイルがある。

f:id:kengo700:20170623202340p:plain

resources_00003.-5とresources_00004.-5も確認

f:id:kengo700:20170623202719p:plain

f:id:kengo700:20170623202939p:plain

つまりバージョンアップでresources_00002.-5に新しいフォントが入り、その分ずれている?

試しにv1.0.3.1のresources_00002.-5とv1.0.3.4のresources_00003.-5を比較してみると、やっぱりそうっぽい

f:id:kengo700:20170623203244p:plain

アンパックしたファイルを確認してみると、v1.0.3.1には無かったresources_00005.-5がある。

f:id:kengo700:20170623203608p:plain

つまり新バージョンのresources_00002.-5はいじらずに、resources_00001.-5、resources_00003.-5、resources_00004.-5、resources_00005.-5、sharedassets0_00001.-1を修正すれば、うまくいくんじゃなかろうか

というわけで、resources_00003.-5の修正、上がv1.0.3.1のresources_00002.-5、中がv1.0.3.4のresources_00003.-5、下が修正してリネームした日本語フォント。

f:id:kengo700:20170623204111p:plain

f:id:kengo700:20170623204448p:plain

resources_00004.-5の修正、上がv1.0.3.1のresources_00003.-5、中がv1.0.3.4のresources_00004.-5、下が修正してリネームした日本語フォント。

f:id:kengo700:20170623204706p:plain

f:id:kengo700:20170623205003p:plain

resources_00005.-5の修正、上がv1.0.3.1のresources_00004.-5、中がv1.0.3.4のresources_00005.-5、下が修正してリネームした日本語フォント。

f:id:kengo700:20170623205132p:plain

sharedassets0_00001.-1の修正、上がv1.0.3.1、中がv1.0.3.4、下が修正した日本語フォント。

f:id:kengo700:20170623205330p:plain

f:id:kengo700:20170623205531p:plain

昨日更新した作業所からtsv形式で翻訳データをダウンロードし、リパックを実行したところエラーが発生。

f:id:kengo700:20170623210124p:plain

IDが見つからなかった時の処理をちゃんとしてなかったことには目をつぶりつつ、IDを出力するようにしてもう一度。

f:id:kengo700:20170623210833p:plain

作業所を修正するときに、スプレッドシートの補完機能で「CREDITS_TRANSLATE_CHINESE」が「CREDITS_TRANSLATE_CHINESE_NAME」になってしまっていたことが原因か。やっぱり寝不足の状態でする作業じゃなかった。

f:id:kengo700:20170623211042p:plain

もう一か所、IDが半角スペースになっていたテキストが抜けていたのを修正。

f:id:kengo700:20170623211546p:plain

エラー無くリパック完了。IDが見つからなかった時の処理については、そのうちプログラムを修正しよう(←いつまでも修正しないパティーン)。

リパックした「resources.assets」と「sharedassets0.assets」をゲームフォルダへ上書きし、ゲームを起動できることを確認。

f:id:kengo700:20170623212210p:plain

「ゲームファイルの整合性を確認」で英語版に戻し、udm差分ファイル作成ツールで差分パッチを作成。

念のため一度英語版で起動できることを確認し、差分パッチを実行。

f:id:kengo700:20170623212942p:plain

ゲーム開始から最初のダンジョンまでと、ボス前からエンドロール終わりまでプレイし、問題ないことを確認。

f:id:kengo700:20170623214417p:plain

パッチをアップロードし、作業所とWikiを編集。

アプデ対応

6/27

v1.0.3.7へのアップデートが来たので確認(いつの間にかv1.0.3.5も来てたらしい…)

テキストについては修正0、追加2、削除0。まあ、そう頻繁にテキストが修正されることもなかろう… 助かる…

f:id:kengo700:20170627195706p:plain

次にフォントデータを確認。

上がv1.0.3.4、下がv1.0.3.7。

f:id:kengo700:20170627200106p:plain

f:id:kengo700:20170627200143p:plain

resources_00002.-5は使ってないけど、一応比較

f:id:kengo700:20170627200221p:plain

f:id:kengo700:20170627200308p:plain

f:id:kengo700:20170627200332p:plain

f:id:kengo700:20170627200411p:plain

f:id:kengo700:20170627200436p:plain

f:id:kengo700:20170627200506p:plain

f:id:kengo700:20170627200540p:plain

f:id:kengo700:20170627200602p:plain

日本語化テストパッチの座標データの変更箇所を手動で修正。リパック。

上書きしてゲーム起動。起動できることを確認。

f:id:kengo700:20170627202630p:plain

「ゲームファイルの整合性を確認」で英語版に戻し、udm差分ファイル作成ツールで差分パッチを作成。

念のため一度英語版で起動できることを確認し、差分パッチを実行。

ゲーム開始から最初のダンジョンまでと、ボス前からエンドロール終わりまでプレイし、問題ないことを確認。このボス倒すの、何度目だ…

パッチをアップロードし、作業所とWikiを編集。

徹夜明けの作業なので、ミスがないかちょっと心配…

有志翻訳プロジェクト終了!

8/4~

無料DLC「Hidden Dreams」の配信にあわせて、日本語(BETA)が配信された!

f:id:kengo700:20170804092137p:plain

f:id:kengo700:20170804092157p:plain

f:id:kengo700:20170804092208p:plain

f:id:kengo700:20170804092222p:plain

タイトルロゴにも「ホロウナイト」の文字が入っていい感じ。 ただしまだBETA版なので、多少文字化けがあるっぽい。

f:id:kengo700:20170804092357p:plain

f:id:kengo700:20170804092414p:plain

しかしプレイには全く問題なさそうなので、有志翻訳プロジェクトは終了としてしまおう。

Wikiを修正。昔の内容は「過去ログ」として折り畳んでおくことにする。

f:id:kengo700:20170804092542p:plain

作業所を修正。共有設定で閲覧のみ可能に。

Twitterで告知。

皆さまお疲れさまでした!

おわりに

いざ日本語化に取り組んでみると、中国人の中文化MODの作成スピードにビビります…

想像ですけれど、専門知識を持った方々が組織的に中文化を行っているのではないかと思います。 今回私が調べながら試行錯誤しているような内容も、ノウハウが共有されているのではないでしょうか。

うらやましいですね…

【4/16追記】

有志翻訳のフォント作成は今回が初めてだったので、しんどかったです。

もちろんゲームによりますけれど、ゲームの翻訳はただテキストを翻訳するだけじゃないんですね…

海外のゲームがなかなか日本語化されない理由がよくわかりました…