NOOB UNITY

初心者がUnityでなんかしちゃうぞBlog

【JSON】 Unityのゲームでセーブとロードを実装したい!

f:id:yuu9048:20190526161733g:plain

ゲームを作る上で大切なのは、データの保管です。せっかく遊んだのにデータが保存されないゲームでは勿体無いですよね。

Unityではデータの保存には色々なアプローチがあります。一番お手軽なのはPlayerPrefsというアプローチ。intやstring型のデータを保存することができます。 たとえば、シューティングゲームのハイスコアやステージのクリアフラグなど単体で扱う簡単なデータならこれで管理してしまえばあっという間にデータの保存と読み込みを実現可能です。

しかし、基本的にローカルに保存する設定保存のためのような使い方がおそらく本筋であり、複雑化するゲームデータを保存するのには色々な工夫が必要となってきます。たとえば複数セーブに対応しようと思うと大変な労力が必要となります。、

そこで、今回はよく言われているJSONを使って外部ファイルにデータを保存し、それを読み込んだりするのに挑戦してみたいと思います。レッツ実装セーブ&ロード!

クリッカーゲームを作る

おいおいまたかよと思われるかもしれませんが、今回もクリッカーゲームを作りながらJSONについて学んでいきます。 今回はより簡略化して、uGUIのボタンをメインに使ってしまおうかと思います。

下準備

まずはさくっと下地をつくります。uGUIでボタンを1個作ってシーンに配置していきましょう。 デザインなどは特になんでも良いです。今回は「押しちゃいけないボタンを押す」という画期的なゲームになります。(どこかで聞いたことある内容ですね…)

f:id:yuu9048:20190526141528p:plain

ついでにテキストも置いておきます。

f:id:yuu9048:20190526150832p:plain

コードを書くぞ(クリッカー編)

JSONのためにクラス構造でデータを扱うと良いらしいので、クラスを作っていきます [System.Serializable] をつけて宣言しないといけないらしいのでそれをカバーしていきましょう。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class Test : MonoBehaviour 
{

    [SerializeField] Text counterText;

    [System.Serializable]
    public class PlayerData {
        public int clickCount;
        public string playerName;
    }

    PlayerData myData = new PlayerData();

    public void OnClickEvent() {
        myData.clickCount++;
        counterText.text = myData.clickCount.ToString();
    }
}

ここの記述でJSONに渡すためにデータ構造を定義しています。下準備のようなものですね JSONシリアライズするために必要らしいのでつけています。あとはPlayerDataというクラスがあり、そのフィールドにはclickCountplayerNameというものがあるだけです。

    [System.Serializable]
    public class PlayerData {
        public int clickCount;
        public string playerName;
    }

これを書いてボタンにアタッチ、ボタンのOnClickでOnClickEventを呼ぶようにしています。 ついでにテキストもアタッチしておきましょう!

f:id:yuu9048:20190526152139p:plain

これで基本的なクリッカーのシステムができあがりました f:id:yuu9048:20190526153135g:plain

セーブシステムを作る

プレイヤーネームをつけてファイルを保存し、そして読み込めるようにしていきたいと思います。 各種UIを良い感じに配置していきましょう

f:id:yuu9048:20190526153850p:plain

ちょっと雑なやり方にはなりますが、さっきのクリッカーボタンのスクリプトを編集してInputFieldへの参照も確保しておきます

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class Test : MonoBehaviour 
{
    [SerializeField] InputField inputArea;
    [SerializeField] Text counterText;

    [System.Serializable]
    public class PlayerData {
        public int clickCount;
        public string playerName;
    }

    PlayerData myData = new PlayerData();

    public void OnClickEvent() {
        myData.clickCount++;
        counterText.text = myData.clickCount.ToString();
    }
}

セーブ機能を書いていく

下記サイトを参考にセーブする仕組みを書いていきます www.sejuku.net

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.IO;

public class Test : MonoBehaviour 
{
    [SerializeField] InputField inputArea;
    [SerializeField] Text counterText;

    [System.Serializable]
    public class PlayerData {
        public int clickCount;
        public string playerName;
    }

    PlayerData myData = new PlayerData();

    public void OnClickEvent() {
        myData.clickCount++;
        counterText.text = myData.clickCount.ToString();
    }

    public void SavePlayerData() {
        StreamWriter writer;
        var playerName = inputArea.text;
        myData.playerName = playerName;

        string jsonstr = JsonUtility.ToJson(myData);

        writer = new StreamWriter(Application.dataPath + "/save"+ playerName + ".json", false);
        writer.Write(jsonstr);
        writer.Flush();
        writer.Close();
    }
}

string jsonstr = JsonUtility.ToJson(myData); でいよいよJSONを利用しています。PlayerData型で作ったデータ構造体をJSON型に変換しています。 たった一行で実装できるのでとっても簡単ですね。

さっき作成したセーブボタンのOnClickにSavePlayerDataをアタッチしておきます。 それでは名前をつけて保存してみましょう!!

f:id:yuu9048:20190526155429p:plain

f:id:yuu9048:20190526155723p:plain

セーブされているのが確認できました!

ロードシステムを作る

JSONにしてセーブするのですから、ロード機能を作るには逆です。JSONからクラスデータに戻してあげればOKですね。

    public void LoadPlayerData() {
        string datastr = "";
        var playerName = inputArea.text;
        StreamReader reader;

        reader = new StreamReader(Application.dataPath + "/save" + playerName + ".json");
        datastr = reader.ReadToEnd();
        reader.Close();

        myData = JsonUtility.FromJson<PlayerData>(datastr); // ロードしたデータで上書き
        Debug.Log(myData.playerName + "のデータをロードしました");
        counterText.text = myData.clickCount.ToString();
    }

入力されている名前のセーブデータを読み込んできます。その後はJsonUtility.FromJson<PlayerData>(datastr)で読み込んだデータを、PlayerData型に変換しているイメージです。 ロードしたデータをそのまま現在のデータに上書きして、テキストエリアにも反映しています。あとはロードボタンに対してLoadPlayerDataをアタッチしておきましょう。

実践編

それでは、さっきセーブしたデータをロードしてみましょう!

f:id:yuu9048:20190526161351g:plain

画面の通り、セーブしたデータをちゃんと読み込むことができています。 見づらいですがコンソール画面には「PlayerName」のデータを読み込んだログが表示されているので、ちゃんとファイルから各種データが読み込めていることがわかります。

それでは一連の流れでやってみましょう! f:id:yuu9048:20190526161733g:plain

  1. カウンターを5回になるまでクリック
  2. [test1]でセーブ
  3. さらに5回クリック
  4. [test2]でセーブ
  5. さらに5回クリック
  6. [test1]をロード(カウント5回がロードされる)
  7. [test2]をロード(カウント10回がロードされる)

セーブとロードの基本的な仕組みを無事にゲームとして実装することができました! 違う名前で共存できるので複数セーブデータにも対応できますね。 データの扱いも比較的カンタンなのでほしいデータをすぐ参照したりすることもできて便利そうです。JSONを使った外部ファイルへのセーブデータ出力。ぜひ活用していきたい機能ですね!

【5分でわかる】クリッカーゲームを作りながらEventSystemによるオブジェクトのクリック判定取得を学ぼう

Unityに生まれたからには、誰もが一度は望む動作

「オブジェクトをクリックしたことを検知」

EventSystemとは、それを実現するイケてる機能なのであるッ!

オブジェクトへのクリック判定を取りたくなる時期が必ずある

反抗期ならぬ、判定期です。嘘です。

ユウ(@YuuUnity)です、こんばんは。 さて、Unityでゲームを作り始めると絶対にいつかは通る道として、オブジェクトへのクリック判定を取りたいというときがあります。 よくあるクリッカーゲームや、タップしたオブジェクトをどうにかしたい場合など、色々なことが考えられますよね。画面全体へのタップはUpdateでマウスのClickを監視すれば良いのですが特定のオブジェクトだけってなるとちょっとハードルが上がった気がします。

でも大丈夫。そんな問題を解決してくれる方法があるのです。

クリッカーを作るぞ

f:id:yuu9048:20190517010547g:plain

前から思っていたのですが、前回のルパンのようになにかゲームっぽいものを題材に説明したほうがわかりやすそうなので今回は犬クリッカーゲームを作ります。

  • 画面に犬が表示されている
  • 犬をクリックすると「1わんっ!」「2わんっ!」などと回数とともに吠えてくれる機能
  • 犬以外をクリックしても特に反応しない

という超面白そうなゲームです。ぜひ一緒に作りましょう!

下準備

犬とカメラの準備

2Dでプロジェクトを作り、犬の画像はどこかから探して準備しておきましょう。 探してきた犬画像はヒエラルキーにドラッグしてシーン上に配置しておきます!

f:id:yuu9048:20190517002217p:plain

まずはMainCameraに対してPhysics 2D Raycasterコンポーネントをアタッチしておきます。読んで時の如く、レイキャスト用のコンポーネントです。クリックしたことを判定するためにくっつけておきます。

テキストの追加

EventSystemが必要となるのですが、これはGUICANVASを追加すると自動的に準備してくれたりするので便利です。今回はちょうどテキストを使う予定なので、UIからTextをクリックしてUIパーツを追加してみましょう。

f:id:yuu9048:20190517002627p:plain

すると、CANVASとTextが追加されます。同時にEventSystemさんも準備されました。

f:id:yuu9048:20190517002745p:plain

f:id:yuu9048:20190517003010p:plain

テキストが見づらい位置にあるので少し調整して、準備はバッチリです!

プログラムするぞ編

さて下準備ができたところでプログラムを書いていきたいと思います。ここで整理しておきますが今回やりたいことは下記の通りです。

  • 画像がクリックされたらクリック回数を増やしてテキストを更新する
  • 画像以外がクリックされたら特に何も起こらない

なので、最低でも1個のスクリプトさえあれば実現できそうなのがわかります。つらつらと書いてみましょう。

Test.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; //UIを使うために必要
using UnityEngine.EventSystems; //イベントシステムを使うために必要

public class Test : MonoBehaviour , IPointerClickHandler { // クリックを取得するためのインターフェースを実装
    [SerializeField] Text countText; // Textをアタッチ
    int count;

    void Start() {
        countText.text = ""; // テキスト初期化
    }

    // このスクリプトがアタッチされたオブジェクトがクリックされたときの処理
    public void OnPointerClick(PointerEventData eventData) {
        count++; // 吠えた回数を増加
        countText.text = count + "わんッ!"; // テキストを更新
    }
}

f:id:yuu9048:20190120165309j:plain

なんか見たこと無い文字列があるぅうう!!

一番怖いやつですね。でも大丈夫です。簡単です。

まずは上の行から見ていきましょう。

using UnityEngine.UI; //UIを使うために必要
using UnityEngine.EventSystems; //イベントシステムを使うために必要

usingで色々と必要なものを取り込んでいます。今回はTextを使うのでUIを読み込んでいるのとクリックした判定を取るためにEventSystemが必要なのでそれも取り込んでいます。 正直それだけなので特に細かい説明も必要ないかなと思います。下準備みたいなものだと思ってください。

public class Test : MonoBehaviour , IPointerClickHandler { // クリックを取得するためのインターフェースを実装

さぁ、見慣れないものがでてきました。IPointerClickHandlerです。ハンドラー? なんか手的なもの? とか色々はてなが浮かびますがほぼ名前の通り「ポインタークリック」ということでクリックされたときに呼ばれる処理のアレです。

この機能っていうのはUnityのEventSystem側で最初から準備されているものなので、usingしてあげればスクリプトに記述して使うことができるようになるっていう寸法です。インターフェースなので実装してあげるだけで利用できるので簡単です。 ただしこれだけでは機能しないのでインターフェースのメソッドを実装していきます。

    // このスクリプトがアタッチされたオブジェクトがクリックされたときの処理
    public void OnPointerClick(PointerEventData eventData) {
        count++; // 吠えた回数を増加
        countText.text = count + "わんッ!"; // テキストを更新
    }

さて、ここが今回のきもとなる部分です。OnPointerClickというのがクリックされた時に呼ばれるメソッドで先程のIPointerClickHandlerインターフェースを実装した際に定義する必要のあるメソッドでもあります。インターフェースを実装したならこのメソッドがないと怒られるのでちゃんと作りましょう。

PointerEventData eventDataというのがなんだか怖いしややこしそうですが、なんのことはない。クリックした際に色々もってきたイベントデータが色々入っています。クリックされた位置とかも取れるはずなのでそれらを使うとまた違った遊びも実現できます。が、今回は割愛します。

メソッドの中身は超シンプル。カウントを増やして、それをテキストに反映しているだけです。やだ…私のスクリプト簡単すぎ…?と思うかもしれませんがこれで動くのでOKです。 さて、あっさりと完成したこのスクリプトをさきほど配置した画像にアタッチしてあげましょう。

クリッカー堂々の完成です!

遊ぶぞ編

さっそく遊んでみましょう。o(´∀`)oワクワク

f:id:yuu9048:20190517005645g:plain

f:id:yuu9048:20190517005844j:plain

なんと動きません。ふざけるな!不良品じゃないか!

あれ…なにか忘れているような気がします。

修正するぞ編

上の方で触れていましたが、Raycastのコンポーネントをつけていました。つまりそれってRayを発射してコライダーにあたらないといけないわけですよね。 …コライダーってつけてありましたっけ? ……いいえ、つけてませんでした。

茶番はこれぐらいにして、画像にコライダーをつけておかないとクリックの判定が取れないので駄目だったというオチです。さっそくBox Collider 2Dをつけてみましょう! 実際のコリジョン判定は必要ないのでTriggerで大丈夫です。

f:id:yuu9048:20190517010133p:plain

さぁ、今度こそ遊んでみましょう。

f:id:yuu9048:20190517010547g:plain

やった、動きました!

画像をクリックしたときだけ数字が増えています。 クリッカーゲームをあっという間に完成させてしまいました! というようにEventSystemさんを使うとクリックの取得などはあっというまに秒殺KO。即実装できてしまうということがわかりました。

今回のクリック以外にもマウスボタンをクリックして下げたとき IPointerDownHandler や、下げたあとに上げたとき IPointerUpHandler を取得するハンドラーもあったりしますし ドラッグの開始を取得するIBeginDragHandler だったり ドラッグ中を取得する IDragHandler や、ドラッグ終了を取得する IEndDragHandler などもあります。

とにかくイベントシステムを使うとプレイヤー側の入力をより簡単に取得することができるようになるので使い勝手が良好です。 ぜひ、クリッカーゲームの改良でもよいですし違うゲームの作成などにもこちらの機能を使ってみてください!