Unity(C#)初心者・入門者向けチュートリアル ひよこのたまご

AndroidやiOS向けアプリを簡単に作れるゲーム開発環境Unity(ユニティ)の使い方を、チュートリアル方式で一緒に学びましょう!

【Unity】2Dタイルマップ15 配置したTileをJsonにセーブ/ロードする

Unity 2020.2.1f1 Personal(2021年3月)
f:id:hiyotama:20210325020224p:plain

前回の続きです!
今回は配置したTileをJsonデータとして保存する、保存したJsonデータからTileを復元する機能を実装します。

TilePaletteとTilemapの準備

まずはTilePaletteにTileを登録し、Tilemapを作成します。
TilePaletteの作成・Tileの登録・IsometricなTilemapの作成はこちらの過去記事をご参照下さい。

hiyotama.hatenablog.com

今回は以下の2つのTileを使います。

f:id:hiyotama:20210318215309p:plain
field_block.png

f:id:hiyotama:20210318215314p:plain
stone_block.png

f:id:hiyotama:20210325015533p:plain
TilePaletteに登録

TilemapのCell LayoutはIsometricで作成、特別な設定は必要ありません。

f:id:hiyotama:20210325015705p:plain
Isometricで作成

Tilemapの作成が完了したら適当にTileを配置します。
Tileなしにも対応させるので、いくつかわざとTileを抜いています。

f:id:hiyotama:20210325020224p:plain
適当にTileを配置

TileとTileの頭文字を保存したScriptable Objectを作成する

Tileの情報をJsonに保存する際データを軽くするため、またデータを見やすくしたいです。
ということでTileとTileの頭文字を保存したScriptable Objectを作成していきます。
Scriptable Objectについてはこちらの過去記事をご参照下さい。

hiyotama.hatenablog.com

まずはTileScriptableObject.csを作成します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.Tilemaps;


[CreateAssetMenu]
public class TileScriptableObject : ScriptableObject
{
    public List<TileStore> tileDataList = new List<TileStore>();
}

[Serializable]
public class TileStore
{
    public string head;
    public Tile tile;
}

TileScriptableObject.cs

作成しましたら、ProjectビューからCreate > Tile Scriptable Objectを選択しScriptable Objectを作成します。

f:id:hiyotama:20210325021836p:plain

作成したScriptable Objectを選択し、InspectorビューにてTilePaletteに登録したTileを登録します。

f:id:hiyotama:20210325022233p:plain
Head: f Tile:field_block
Head: s Tile:stone_block

以上でScriptable Objectの作成は完了です。

Tileの位置情報をJsonに保存する

続いてTileをJsonに保存していきます。
Unity標準のJsonUtilityを使っていきます。
JsonUtilityについてはこちらの過去記事をご参照下さい。

hiyotama.hatenablog.com

hiyotama.hatenablog.com

それではControllerという名前の空オブジェクトを作成し、TileSaveController.csというScriptを作成し取り付けます。

using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Tilemaps;


public class TileSaveController : MonoBehaviour
{
    [SerializeField] Tilemap tilemap;
    [SerializeField] TileScriptableObject tileSB;

    const string SAVE_FILE = "tilemap.json";
    const string DATA_DIR = "Assets/StreamingAssets/data/";
    static string saveDataPath = Path.Combine(DATA_DIR + SAVE_FILE);


    private void Start()
    {
        Save();
    }


    public void Save()
    {
        var data = new SaveTilemapData();

        tilemap.CompressBounds();
        var b = tilemap.cellBounds;

        string str = "";
        for (int y = b.min.y; y < b.max.y; y++)
        {
            for (int x = b.min.x; x < b.max.x; x++)
            {
                if (tilemap.HasTile(new Vector3Int(x, y, 0)))
                {
                    str += tileSB.tileDataList.Single(t => t.tile == tilemap.GetTile(new Vector3Int(x, y, 0))).head + ",";
                }
                else
                {
                    str += " ,";
                }
            }

            str = str.TrimEnd(',');
            data.mapData.Add(str);
            str = "";
        }

        string json = JsonUtility.ToJson(data, true);
        if (!Directory.Exists(DATA_DIR))
        {
            Directory.CreateDirectory(DATA_DIR);
        }

        StreamWriter writer = new StreamWriter(saveDataPath, false);
        writer.WriteLine(json);
        writer.Flush();
        writer.Close();
    }


    [Serializable]
    public class SaveTilemapData
    {
        public List<string> mapData = new List<string>();
    }
}

TileSaveController.cs

Inspectorビューによりtilemapに作成したTilemapを、tileSBに作成したScriptableObjectを登録します。

f:id:hiyotama:20210325023224p:plain
TileMapとScriptableObjectを登録

Jsonに保存するSaveTilemapDataクラスを作成し、Tileの位置情報を保存する文字列List(mapData)を用意します。
xとyを組み合わせたfor文を回し、全てのTileが収まる範囲のTile情報をカンマ区切りで保存しています。
指定した位置にTileがある場合はtileSB.tileDataListからLinqのSingleメソッドを活用し、同じTileのhead情報(アルファベット1文字)を抽出し追加します。
指定した位置にTileがない場合はスペースを追加します。

全ての情報をmapDataに保存したらJsonUtility.ToJsonメソッドでJson化し、指定したディレクトリに保存します。

ProjectビューのAssets/StreamingAssets/data/tilemap.jsonが作成されているはずです。

{
    "mapData": [
        " , , , , ,f,f,f,f,f,f, ",
        " , ,f,f,f,f,f,f,f,f,f, ",
        " ,f,f,f,s,f,f,s,f,f,s,f",
        "f,f, ,f,s,f,f,s,f,f,s,f",
        "f,f, ,f,s,f,f,s,f,f,s,f",
        "f,f, ,f,s,f,f,s,f,f,s,f",
        "f,f,f,f,s,s,s,s,s,s,s,f",
        "f,f,f, ,f,f,f,f,f,f,f,f",
        "f,f,f, ,f,f,f,f,f,f,f, ",
        "f,f,f,f,f,f,f,f,f,f, , ",
        " ,f,f,f, , , , , , , , "
    ]
}

tilemap.json

Tileがある場所はTileScriptableObjectのheadが、Tileがない場所はスペースが記入されたJsonが保存されました。

Tileの位置情報保存については以上です。

Tileの位置情報をロードする

最後に保存したJsonを活用してTileをロードしていきます。
まずはSceneビューにて配置したTileを全て削除します。

f:id:hiyotama:20210325024556p:plain
Tileを削除

続いてTileSaveController.csにLoadメソッドを追加します。

using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Tilemaps;


public class TileSaveController : MonoBehaviour
{
    [SerializeField] Tilemap tilemap;
    [SerializeField] TileScriptableObject tileSB;

    const string SAVE_FILE = "tilemap.json";
    const string DATA_DIR = "Assets/StreamingAssets/data/";
    static string saveDataPath = Path.Combine(DATA_DIR + SAVE_FILE);


    private void Start()
    {
// ***** 修正 *****
        // Save();
        Load();
// ***** 修正終了 *****
    }


    public void Save()
    {
        var data = new SaveTilemapData();

        tilemap.CompressBounds();
        var b = tilemap.cellBounds;

        string str = "";
        for (int y = b.min.y; y < b.max.y; y++)
        {
            for (int x = b.min.x; x < b.max.x; x++)
            {
                if (tilemap.HasTile(new Vector3Int(x, y, 0)))
                {
                    str += tileSB.tileDataList.Single(t => t.tile == tilemap.GetTile(new Vector3Int(x, y, 0))).head + ",";
                }
                else
                {
                    str += " ,";
                }
            }

            str = str.TrimEnd(',');
            data.mapData.Add(str);
            str = "";
        }

        string json = JsonUtility.ToJson(data, true);
        if (!Directory.Exists(DATA_DIR))
        {
            Directory.CreateDirectory(DATA_DIR);
        }

        StreamWriter writer = new StreamWriter(saveDataPath, false);
        writer.WriteLine(json);
        writer.Flush();
        writer.Close();
    }

// ***** 追加 *****
    public void Load()
    {
        tilemap.ClearAllTiles();

        FileStream stream = File.Open(saveDataPath, FileMode.Open);
        StreamReader reader = new StreamReader(stream);
        var json = reader.ReadToEnd();
        reader.Close();
        stream.Close();
        SaveTilemapData data = JsonUtility.FromJson<SaveTilemapData>(json);

        for (int y = 0; y < data.mapData.Count; y++)
        {
            string[] xlist = data.mapData[y].Split(',');
            for (int x = 0; x < xlist.Length; x++)
            {
                if (xlist[x] == " ") continue;
                tilemap.SetTile(new Vector3Int(x, y, 0), tileSB.tileDataList.Single(t => t.head == xlist[x]).tile);
            }
        }
    }
// ***** 追加終了 *****


    [Serializable]
    public class SaveTilemapData
    {
        public List<string> mapData = new List<string>();
    }
}

TileSaveController.cs

Loadメソッドでは保存したjsonデータをJsonUtility.FromJsonにてSaveTilemapDataクラスに復元しています。
データをfor文で回し、Tilemap.SetTileメソッドでTileを配置していきます。
セーブの時と同様、LinqのSingleメソッドでScriptableObjectと一致したTileを配置します。
スペースが来た時はcontinueを行い配置を回避します。

f:id:hiyotama:20210325020224p:plain
セーブしたTileの位置情報通りに配置されます

今回は以上になります。
ありがとうございました〜。