[解説]「Windowsの設定がフランス語だとゲームが起動しない」開発者がXで公開した小数点表記に関するバグの詳細をレポート
[UPDATE 2023/08/17]
TryParse()系の例外をキャッチして処理するコードを省略していましたが、表記があったほうがいいというコメントをいただいたため、追記を行いました。
ゲーム開発を含むプログラミングにおいては、意外なところに落とし穴がたくさんあります。今回紹介するのは「フランス語のローカライズ対応における小数点問題」です。
発端となったポスト
今回の記事は、筆者(せきやdn)がX(旧Twitter)にポストした次のツイートが発端です。
本ポストが1,000を超えるリポストをしていただき、この仕様への関心がとても高いことがわかりました。そこで本記事では、ポストでは書ききれなかった本件の詳細についてご紹介します。
小数点問題とは
ゲームを開発して海外を含めて配信する場合、当然ですが国や地域・文化によって使う言語が違いますので、それぞれの言語へのローカライズが必要です。そして、言語が違うと文字に関するルールも違います。今回のポイントは小数点の表記方法の違いです。例えば日本語では、
2.5
というように「.」で整数部分と小数部分を区分けします。ところがこれが言語によっては、
2,5
と表記することがあります。整数部分と小数部分の区分けに「,」カンマを使う言語があるということです。今回私が直面した小数点の表記方法の違いによる不具合の要因でした。
不具合に気づいた経緯
事の発端は私がブラウザ版からSteam・NintendoSwitch版への移植を担当している『ファミレスを享受せよ』というゲームです。先日、Steamでは販売が開始されました。パブリッシャーの『わくわくゲームズ』さんがバグ報告のフィードバックスレッドを立ててくれたのですが、その中の報告に「ゲームが起動後、ブラックアウトして進まない」というものがありました。
https://steamcommunity.com/app/2336980/discussions/0/3805030452330187752/
ゲーム内で起こる不具合ならいざ知らず、遊べないとは穏やかではありません。
報告いただいた方のPCが日本語環境ではなかったので、お願いして言語周りの設定変更を試してもらいました。すると「Windowsの言語設定をフランス語から英語」に変えてみたところ無事に起動できたということでした。最初はそんなバカなと思ったのですが、私の開発PCのWindowsの言語設定をフランス語にしたところ、ゲームがブラックアウトして進まないという状態になりました。
エラーになるソースコード
『ファミレスを享受せよ』のSteam・NintendoSwitch版は、ゲームエンジンUnityで開発を行っています。プログラミング言語はC#です。Unityを立ち上げてエラーの箇所を調査してみると、
number = float.Parse("2.5")
という文字列を数値に変換する行でエラーが発生していました。(上記のコードは例です)
Windowsの言語設定がフランス語だとエラーで停まり、日本語では問題なく動作します。
この原因を調べたところ、
「フランス語では小数にカンマを使っているため、カンマの無い小数は float.Parse でエラーになる」
というものでした。
厳密にはフランス語だけでなく、小数にカンマを使用するすべての言語でエラーになるようです。アプリ実行時のWindowsの言語設定によって挙動が変わる、というのはお恥ずかしい話なのですが、あまり考慮していませんでした。何の気なしに使用していた文字列→小数変換の処理にこんな落とし穴があったとは…。
対処方法
ではどのように修正すればよいかですが、結論を申し上げると TryParse を適切な引数と共に使用します。
using System.Globalization; //ソースコード上部に記述
//変換エラーに対応する記述の例
bool result = float.TryParse("2.5", NumberStyles.Number, CultureInfo.InvariantCulture, out number);
//TryParse失敗
if(result == false)
{
//ログを出力して停止するなどゲームごとのエラー処理
}
1つめの引数は変換したい文字列です。2つめの引数はNumberStyles列挙体のNumberを指定します。変換する文字は数値として扱うという設定にします。3つめの引数が重要で、CultureInfo.InvariantCulture を設定します。これはカルチャ(国/地域)に依存しない形でデータを保持できる設定です。詳しくは.NETのドキュメントをご覧ください。まさに今回のようなケースに対応する設定になります。4つめの引数は変換した数値を入れる変数です。繰り返し使用する場合は関数化しておくとよいでしょう。
TryParseは戻り値で成否を返します。失敗した場合はbool型のfalseが返ってくるので、開発ゲームに即したエラー処理へ遷移するようにしましょう。(2023/08/17追記)
また、対応方法の資料として、獏星(ばくすたー)氏が開発している「VMagicMirror(WindowsでVRMを表示し、追加のデバイスなしで動かせるアプリケーション)」に同様の仕様について対応を行ったプルリクがあります。こちらも対応方法の参考になります。
https://github.com/malaybaku/VMagicMirror/pull/583/commits/df35efd762ecf56611c2fa13c84f8e85a3722afc
獏星(ばくすたー)氏の場合はfloat.TryParseを使わずに、CultureInfo.InvariantCultureを使った自前実装メソッドで対応を行っています。
まとめ
経緯と対処方法は以上です。正直なところ普段から.NET環境で開発をしているIT系技術者の方からすれば、よくあるケースだったかもしれません。しかし業界を経験せず、Unityでゲーム開発をしている方においては初耳という方も多かったのではないでしょうか。今回は小数点のケースでしたが、別のシチュエーションでもエラーが発生する可能性はあります。海外展開を検討している開発者の方は、
・OSの言語設定で挙動が変わるプログラムがあることを知っておく
・OSの言語変更をしてテストプレイする必要がある
ということを忘れないようにしましょう。(と自分に言い聞かせています…)
自分でバグを生んで記事を書いては世話がないですが、私の今回の苦い経験が皆さんのゲーム開発の手助けになれば幸いです。