【ゲーム開発のためのC#入門講座・応用強化編】データをJSON形式でセーブ&ロードしよう(前編)【#4】

6.0_C#応用強化編

Unity標準セーブ&ロード機能は非推奨

ゲームの種類にもよるのですが、Unityに標準で用意されているセーブ&ロード機能である「PlayerPrefs」は非推奨です。

理由はシンプルで、ツールを使えば外部から簡単に改ざんできてしまうからです。

ゲームの進行度やキャラクターステータス、オンライン要素のあるゲームであればハイスコアなどは勝手に書き換えられてしまうと非常に困りますよね。

また、「SetInt」「SetFloat」「SetString」というメソッド名からもわかる通り、変数単位のデータを保存する想定で作られているため、大量データの保存にも不向きです。

では、どのようにゲームデータを取り扱うかというと、保存したいデータをすべてテキストデータに変換し、それをコンピュータやスマホ内に保存します。

ただのテキストデータなので、メモ帳とかで開けます。

例えば下記クラスのインスタンスデータを保存すると、

public class Test 
{
    public string Name {get; set; } = "hoge";
    public int ClearLevel {get; set; } = 5;
    public int HighScore {get; set; } = 100;
}

ファイルの中身はこんな感じになります。

{
    "Name": "hoge",
    "ClearLevel": 5,
    "HighScore": 100
}

うわ、意外と原始的、なんて思った方もいるかもしれませんね。

実際にはプレイヤーに改ざんされないように暗号化を施した上で、少しでもファイルサイズを軽量化するためにスペースや改行などはすべて削除して圧縮してしまいます。なので出力結果はもっと複雑だし、外から見る分には理解できるような文字の並びにはなっていません。

// ↓実際にはこんな風に暗号化・圧縮されているから、ぱっと見はよくわからないよ!
WY+AIOrYfGMgxmhe4D5dB5tuF0M+NR5baS2n0q9UGY0HtZ8z+...

ただ、逆にいえば暗号化を解除すると、先程のようなシンプルなデータ構造になっているということです。根っこは単純なんだなーと思うと、少し安心できますね。

このシンプルな構造で記述されたデータ形式のことをJSONと言います。

JSONって?

JSONはJavaScript Object Notationの略。

その名の通り、もともとはJavaScriptというプログラミング言語でデータのやり取りをするのに使われていました。Webで扱う性質上、軽量かつ高速に処理できるよう作られた結果、現在ではJavaScriptに限らず様々な言語で利用されています。

今回はこのJSON形式でデータを読み書きする方法を、前後編に分けてご紹介します。

ファイルを出力する方法を学習しよう

まずはただの文字列をテキストデータとして出力する方法からいきましょう。

実際の実装例です。

using System.Text;
​
string folderPath = @"C:\Users\kaza_\Desktop\test";
string fileName = "csharpTest.txt";
​
string filePath = Path.Combine(folderPath, fileName);
​
using (FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))
{
    using (StreamWriter streamWriter = new StreamWriter(fileStream, Encoding.UTF8))
    {
        streamWriter.WriteLine("hoge");
    }
}

なんか見慣れないものが色々ありますが、まずは保存したいファイルパスを皆さんの環境に合わせて変更してください。

// ↓@"保存したいファイルパス"を設定してね!
//  実在するフォルダじゃないとエラーになってしまうので気を付けて!
string folderPath = @"C:\Users\kaza_\Desktop\test";

その上で、実際に実行してみましょう。

うまくいけば指定したフォルダに「csharpTest.txt」というファイルが作成されており、開いてみると「hoge」という文字列が書き込まれているはずです。

もし「System.UnauthorizedAccessException」というエラーが発生してしまった場合は、対象フォルダにファイルを作成する権限がなくて実行できないことを意味するため、別のフォルダを指定してあげてください。

それでは、コードの意味をひとつずつ理解していきましょう。

using System.Text;
​
// ①フォルダパスの@について
string folderPath = @"C:\Users\kaza_\Desktop\test";
string fileName = "csharpTest.txt";
​
// ②Path.Combineについて
string filePath = Path.Combine(folderPath, fileName);
​
// ③usingについて、④FileStreamクラスについて
using (FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))
{
    // ⑤StreamWriterクラスについて
    using (StreamWriter streamWriter = new StreamWriter(fileStream, Encoding.UTF8))
    {
        streamWriter.WriteLine("hoge");
    }
}

①フォルダパスの@について

// ↓何か謎の@マークがついてる!
string folderPath = @"C:\Users\kaza_\Desktop\test";

フォルダパスの先頭に見慣れない@マークがついていますね。

皆さんがもし「"」という記号を文字列の中に使いたいとしたら、例えば「"はダブルクォーテーションと読む」なんて文字列を使いたいとしたらどうしますか?

// ↓おや、文字列の範囲が正しく判定されない!?
string text = ""はダブルクォーテーションと読む";

上記のように単純に文字列の中に含めるだけだと、「"で囲まれている部分を文字列として判断する」というC#の仕様とぶつかってしまうため、正しく認識されなくなってしまいます。

そこでC#では言語内で扱う特殊な記号をただの文字として扱いたい場合、「ただの文字ですよ目印」としてエスケープ文字というものを付与することになっています。

このエスケープ文字が「\」です。

そのため、先程の文字列は「"」の前に「\」をつけると、ひとつの文字列として正しく認識されるようになります。

// ↓特殊な記号の前にはエスケープ文字「\」をつけると、
//  ただの文字として認識してもらえるようになるよ!
string text = "\"はダブルクォーテーションと読む";
​
// ↓出力してみるとエスケープ文字の部分だけが省かれて、
//  「"はダブルクォーテーションと読む」と出力されるよ!
Console.WriteLine(text);

ただ、この時困るのが今回扱っているようなフォルダやファイルのパスです。

フォルダやファイルのパスは区切り目の度に、その境界線を示す記号として「\」が使われていますよね。これが今度はエスケープ文字だと誤認されてしまうのです。

この場合、エスケープ文字である「\」をただの文字として認識してもらうため、自分自身にエスケープ文字を付与する必要があります。

// ↓ただの文字として扱ってもらうため、エスケープ文字をエスケープする!
string folderPath = "C:\\Users\\kaza_\\Desktop\\test";

でもエスケープ文字をエスケープするって何か本末転倒な感じがするし、区切り目の度に毎回エスケープ文字つけるのもぶっちゃけだるいですよね。

そこでC#では、先頭に@マークつけた場合、エスケープ文字はすべてただの文字扱いとして認識する、というルールになっています。

これなら見たままの表示で定義することができますね。

フォルダやファイルパスを文字列として扱いたい場合は基本先頭に@マークをつけるようにしましょう。

②Path.Combineについて

string filePath = Path.Combine(folderPath, fileName);

これはクラス「Path」のstaticメソッド「Combine」です。

ひとつ目の引数とふたつ目の引数を結合してひとつのパスにしてくれます。

③usingについて

// ↓このusingは何?
using (FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))

これC#のよくないところのひとつだと個人的に思っているのですが、実はC#にはふたつのusingがあります

ひとつは以前紹介した、名前空間を省略する機能としてのusing。

そしてもうひとつは今回の、特定条件を満たすインスタンスの場合に何らかの終了処理を自動実行してくれる機能としてのusingです。

// ↓{}の処理が完了すると、自動的に何らかの終了処理を行う
using (特定条件を満たすクラス 変数名 = インスタンス)
{
    ...対象インスタンスを使った処理……
}

詳しいことはもう少し専門的な技術が必要になるため、専門技術編で解説します。

今回は「作成したファイルを自動的に閉じてくれる便利なキーワード」と思ってもらえればOKです。

逆にいうとこのusingをつけておかないと、プログラムが終了してもファイルが開きっぱなしになってしまうので注意です。

④FileStreamクラスについて

// FileStreamクラスって?
using (FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))

FileStreamクラスはファイル形式のデータを扱うためのクラスです。

たくさんのコンストラクタが用意されているので一例となりますが、今回の例でいうと、個々の引数の意味は下記の通りとなります。

  1. ひとつ目の引数は対象となるファイルパス
  2. ふたつ目の引数はファイルの取り扱い方。OpenOrCreateを指定すると、既に存在する場合は対象を上書きし、存在しない場合は新規作成する
  3. ファイルの利用方法。読み取りとして使うか、書き込みとして使うか、両方なのか。今回はWrite(書き込み)として指定

先程もお伝えした通り、使い終わったら閉じる必要があるため、原則usingとセットで使います。

⑤StreamWriterクラスについて

// StreamWriterクラスって?
using (StreamWriter streamWriter = new StreamWriter(fileStream, Encoding.UTF8))

StreamWriterクラスはデータの書き込みを行うためのクラスです。

ひとつ目の引数に指定されたFileStreamクラスのインスタンスを対象とし、ふたつ目の引数で指定された文字コードタイプで書き込みを行います。

文字コードは基礎強化編の第一章で少しだけ触れていましたね。

今回は海外でも扱いやすい「UTF8」という文字コードで書き込むことにします。

また、こちらも使い終わったら閉じる必要があるため、原則usingとセットで使います。

改めてコードを見てみよう

using System.Text;
​
string folderPath = @"C:\Users\kaza_\Desktop\test";
string fileName = "csharpTest.txt";
​
string filePath = Path.Combine(folderPath, fileName);
​
using (FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))
{
    using (StreamWriter streamWriter = new StreamWriter(fileStream, Encoding.UTF8))
    {
        streamWriter.WriteLine("hoge");
    }
}

このコードをひとつずつ追いかけてみると、

  1. 対象となるファイルパスを生成
  2. 対象ファイルを扱うFileStreamクラスのインスタンスを作成(using付)
  3. 対象ファイルに書き込むStreamWriterクラスのインスタンスを作成(using付)
  4. 対象ファイルに書き込み
  5. usingによって対象ファイルが自動的に保存・閉じられる

このような流れになっています。

JSON形式で出力する時もこの構造自体は変わりありません。

ただし、JSONの場合は変換メソッドを利用することで、インスタンスを丸ごと文字列化したり文字列化したものからインスタンスを復元したりできるというところが大きな強みです。

その方法については、次回解説します。

まとめ

  • JSONはJavaScript Object Notationの略。軽量かつ高速に処理できるテキストデータとして、色んな言語で利用されている
  • Unity標準のセーブ&ロード機能は改ざんの恐れがあるため、JSON形式のデータを暗号化して保存することが多い
  • 区切り目の記号「\」がエスケープ文字と誤認されないよう、フォルダやファイルパスを文字列化する場合は最初に@マークをつけておく
  • C#にはふたつのusingがある。ひとつは名前空間の省略、もうひとつは終了処理の自動化
  • FileStreamクラスとStreamWriterクラスを利用することで、外部にテキストデータを作成・書き込み・保存することができる

次回、読み込み方法とJSON形式の取り扱い方法に続きます。

それでは、今回もお疲れ様でした!

また次の記事でお会いしましょう!

Posted by yuumekou