2017年11月20日 星期一

[Unity] 使用內建的JsonUtility 遊戲讀存檔案 --2017.11.20更新


大部分遊戲都會使用到「讀取」、「儲存」狀態與進度,本篇 「Unity 範例檔」示範如何使用Unity內建的 JsonUtility  進行 Json 檔案的讀存取。

JSON檔案的維基百科
P.S 話說JSON的ICON也太帥氣了 >////<
P.S 我在google上面打上『Unity Json 教學』,出來結果都是額外用 LitJSON  這個三年沒更新的插件了...那個沒有內建的速度快而且可能會有些問題...。2017.05.11

P.S 跟各位致歉,剛才發現我提供的教學檔是舊的,裡面並沒有套入Load部份 2017.05.11

P.S 我緊急將教學檔先做更新,網誌內容稍後會在調整。 2017.05.11

有網友回覆說專案建置後Json檔案不會建置出來 @@ 
我上網查了一下... 改將 Json檔案放置到 Assets/StreamingAssets
就可以解決這問題~ 但是缺點是這檔案就是完全沒有被封裝起來,
因此使用者可以很簡單的就用文字編輯器改資料'  2017.09.24

網友再度表示第二版的做法放在PC沒問題,但是輸出成APK就無法作用了!@@
Google 大神再度引領我找到解答: Application.persistentDataPath
使用persistenDataPath的範例檔案我放在下面2017.11.20版了。







[教學檔下載]

連結 2017.09.24 版本 (新版本) (2021.10.12更新連結)


[狀態存檔]
  1. 假設我們現在要存檔下面五項資料
    • 玩家名稱,sammaru 
    • 玩家等級,87級
    • 玩家所處座標,X:33,Y:0,Z:33
  2. 這三項資料的變數型態剛好都是不同變數類型: 
  3. string playerName;   //玩家名稱,sammaru
    int level;           //玩家等級,87級
    vector3 postion;     //玩家座標,vector3(0,0,500)
  4. 為了讓檔案JsonUtility可以讀存這些檔案,
    我們需要建立一個class來承接資訊:
        public class playerState
        {
            public string name;
            public int level;
            public Vector3 pos;
        }
  5. 接下來我們在save程式片段內,建立一個playerState類型的資料「myPlayer」,
    並且將資料寫入myPlayer:
        public void save()
        {
            //填寫jplayerState格式的資料..
            playerState myPlayer = new playerState();
            myPlayer.name = "sammaru";
            myPlayer.level = 87;
            myPlayer.pos = GameObject.Find("sammaru").transform.position;
        }
  6. 接下來,我們繼續在save中,加入把myPlayer資料轉成json的代碼:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
        public void save()
        {
            //填寫jplayerState格式的資料..
            playerState myPlayer = new playerState();
            myPlayer.name = "sammaru";
            myPlayer.level = 87;
            myPlayer.pos = GameObject.Find("sammaru").transform.position;
    
            //將myPlayer轉換成json格式的字串
            string saveString = JsonUtility.ToJson(myPlayer);
    
            //將字串saveString存到硬碟中
            StreamWriter file = new StreamWriter(System.IO.Path.Combine(Application.streamingAssetsPath, "myPlayer"));
            file.Write(saveString);
            file.Close();
        }
    
  7. 上述程式碼不但將myPlayer轉存成字串saveString,還透過StreamWrite將資料存到硬碟。
    為了要使用StreamWrite,必須在程式碼開頭載入相關資源:
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using System.IO;  //StreamWrite會用到
  8. 開啟並執行在本文提供的範例檔,
    在執行狀態中擊該按鈕SAVE,會在Asset/Resources資料夾內產生的myPlayer.json檔案。
    剛檔案的內容如下:
    {"name":"sammaru","level":87,"pos":{"x":0.0,"y":0.0,"z":500.0}}
  9. 格式化後看起來是這樣,存檔成功!
    {
        "name": "sammaru",
        "level": 87,
        "pos": {
            "x": 0.0,
            "y": 0.0,
            "z": 500.0
        }
    }
[狀態讀檔]
  1. 在load函式中,我們填入下面程式碼:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
        public void load()
        {
            //讀取json檔案並轉存成文字格式
            StreamReader file = new StreamReader(System.IO.Path.Combine(Application.streamingAssetsPath, "myPlayer"));
            string loadJson = file.ReadToEnd();
            file.Close();
    
            //新增一個物件類型為playerState的變數 loadData
            playerState loadData = new playerState();
    
            //使用JsonUtillty的FromJson方法將存文字轉成Json
            loadData = JsonUtility.FromJson<playerState>(loadJson);
    
            //驗證用,將sammaru的位置變更為json內紀錄的位置
            GameObject.Find("sammaru").transform.position = loadData.pos;
        }
    
  2. 上述程式碼,是透過StreamRead讀取遊戲目錄路內的myPlayer.json。
    並透過JsonUtility的FromJson將字串解析成物件。
  3. 大家玩玩可以下載本文提供的範例檔,
    透過WASD可以改變sammaru的位置,
    並可按save紀錄其位置,按Load將sammaru還原至按save按鈕時的位置。

38 則留言:

  1. 接下來我們在save程式片段內,建立一個playerState類型的資料「myPlayer」,
    並且將資料寫入myPlayer:
    public void save()
    {
    //填寫jplayerState格"式"的資料..
    playerState myPlayer = new playerState();
    myPlayer.name = "sammaru";
    ....

    *有錯字 格「式」

    回覆刪除
    回覆
    1. 5.6.都有 格「式」可以改XD

      在load函式中 那邊,上方應該是 狀態載檔吧?


      最後...感謝教學!!!

      刪除
    2. 感謝回覆~ 有錯字真是真是不好意思! 有任何錯誤都麻煩提出囉
      再次感謝回覆! 因為你本教學會變更好~~~~~

      刪除
    3. 進到遊戲,還沒Save就Load的話,會出現錯誤
      NullReferenceException: Object reference not set to an instance of an object

      刪除
    4. 下載的範例檔有做修正了,沒想到是錯在我後來多寫的方法 syncInfo();

      刪除
    5. 基本上應該是沒問題了:)
      我在Start() 那邊加入load();
      這樣進入就直接是上次的位置

      然後button是故意用在鏡頭外面嗎XD

      感謝教學!! 這樣應該就沒問題了!

      刪除
    6. 然後button是故意用在鏡頭外面嗎XD
      ~~疑疑疑,晚點偷改一下

      刪除
    7. 哈哈! 然後最後的建構元 還沒用到
      public playerState(string n, int l, Vector3 p)
      這是之後也許會用到加上去的吧?

      刪除
    8. 如果沒有什麼問題的話...那個建構元就放著吧 ^_^

      刪除
  2. 想請問山姆大,如果要用Json儲存遊戲中所有物品資料
    該如何去執行?

    回覆刪除
    回覆
    1. 有沒有實際狀況可以舉例?我在依照你的舉利用個範例會比較直接

      刪除
    2. 作者已經移除這則留言。

      刪除
    3. 目前想在同個json中存取多個物品資料
      {
      "itemID": 0,
      "itemName": "Test00",
      "itemType": "Test",
      "itemDesc": "TestDesc"
      }
      查了資料後使用了這樣的方式去紀錄多個檔案
      [
      {"Name": Hi01}
      {"Name": Hi02}
      ]
      可是卻不曉得如何存入List中
      另外想請問JsonUtility是否不支援中文內容,嘗試後都會變為亂碼 (?
      按到刪除 (艸

      刪除
    4. 中文的問題已經解決,貌似是我的VS跳成ANSI編碼
      改回UTF-8就可以了 <3

      刪除
    5. 最後一個問題也解決了,原來只是我腦抽會錯意
      文章告訴我的是宣告陣列也就是要寫成
      {
      "items":
      [
      "Items/Tools/hoe.json"
      ]
      }
      打擾了抱歉 (#

      刪除
    6. 不要覺得打擾啦 沒人留言我很無聊耶!!
      你是想知道如何把List存入Json中嗎? ^^

      刪除
    7. 其實大致上的概念我都懂,只是很死腦經XD
      卡在一個自己無法理解的點,就壞光光了!!
      現在解決了問題,發問後問題自然就理順了 (?!

      刪除
    8. Great! 恭喜你解題成功

      刪除
  3. 請問這個可以在android上執行嗎? 它的路徑好像不能設在 Application.persistentDataPath 會拒絕存取。
    還有使用內建的跟額外用LitJSON會有甚麼差別?

    回覆刪除
    回覆
    1. 1. 路徑問題:
      http://answers.unity3d.com/questions/1171740/android-devices-does-not-read-json-file-but-unity.html
      看上面文章的討論,看來是要用 Resources.Load 的方式,去讀取放在Assets/Resources/ 的json檔案


      2. 跟LitJSON的差別
      http://jacksondunstan.com/articles/3303
      看這篇的敘述,應該是內建的快很多

      刪除
  4. 請問如何將json load()出來之後 在他底下一個新的數組 再save()回去
    因為我想寫個類似儲存卡牌的json檔 但卻摸不著頭緒

    回覆刪除
    回覆
    1. 很高興看到有人提問!!
      不過我有點不太明白你的意思 ,
      可以再講詳細一點嗎 ?

      刪除
    2. 將json檔讀取出
      {"GirlID":1,
      "GirlName":"AAA",
      "Girlstar":5,
      "GirlLevel":1}
      之後 加入一筆新的
      {"GirlID":2,
      "GirlName":"BBB",
      "Girlstar":4,
      "GirlLevel":1}
      再儲存回去原本的json檔中
      應該說把一筆資料新增道便兩筆資料

      刪除
    3. https://goo.gl/fW6eek
      我寫了一個簡單的範例給你。

      這份範例裡面已經新增了四份資料GirlName資料
      分別是 Rose,Apple,Cindy,Gigi
      你可以透過下方的輸入框輸入GirlName即可查詢出對應的GirlLevel。

      或是你可以試著用上方的輸入框部分。自行新增新的GirlName以及該Girl的Level

      刪除
    4. 謝謝山姆大,看過一次就懂了
      跟我想的寫法完全不一樣,但是非常簡單!

      刪除
  5. 山姆大 請問unity build的時候 要怎麼將json一起build出來

    回覆刪除
    回覆
    1. OK 我測試了一下。json其實是有被Build出來的。 只是被封裝了起來,目前的寫法會讀取不到 @@
      所以我網站上提供的讀檔方式 改成下面這段就可以了:

      public void load()
      {
      //讀取json檔案並轉存成文字格式
      TextAsset file = Resources.Load("myPlayer") as TextAsset;
      string loadJson = file.text;

      //新增一個物件類型為playerState的變數 loadData
      playerState loadData = new playerState();

      //使用JsonUtillty的FromJson方法將存文字轉成Json
      loadData = JsonUtility.FromJson(loadJson);

      //驗證用,將sammaru的位置變更為json內紀錄的位置
      GameObject.Find("sammaru").transform.position = loadData.pos;
      }

      ================
      重點就在於要把原本的:
      StreamReader file = new StreamReader(Application.dataPath + "/Resources/myPlayer.json");

      改成
      TextAsset file = Resources.Load("myPlayer") as TextAsset;

      再把
      string loadJson = file.ReadToEnd();

      改成
      string loadJson = file.text;

      然後 file.Close(); 可以刪掉了,用不到了。

      你再試試看唷 ^_^

      刪除
    2. 謝山姆大 >_< 馬上解決

      刪除
    3. 我後來再多試了一下 這樣解還是怪怪的。
      雖然放在Resources資料夾下會被封裝包起來,但是沒辦法再寫入@@
      (至少我查了一下好像沒辦法)

      所以我直接再換一個做法,就可以讓json包出去了。
      你再看看網頁上面新的程式碼還有範例吧

      刪除
  6. 山姆大 我又來了 >_<
    我根據您上次所說的
    把 StreamReader file = new StreamReader(Application.dataPath + "/Resources/myPlayer.json");
    改成 TextAsset file = Resources.Load("myPlayer") as TextAsset;

    在電腦上面執行是沒問題的
    但是當我包成apk檔放到手機上執行 不知到哪邊有錯誤 我手機上讀取不到json的檔案
    所以整個檔案有用到json的地方全部不會顯示
    再次感謝山姆大 >_<

    回覆刪除
    回覆
    1. OK 我新增了第三個範例檔了,我自己測過在安桌環境上面可讀可寫了。
      您測試有問題或沒問題也麻煩跟我說一下嘿!

      刪除
    2. 我需要的是把json檔一起包進去 所以我可能沒辦法使用這個

      刪除
    3. 那你可以把要讀的Json放在Resources資料夾內。
      用Resources.Load的方式讀取。

      https://answers.unity.com/questions/1092940/loading-json-as-a-text-asset-using-resourcesload.html

      刪除
    4. 抱歉 山姆大 我忘記說還有save的部分
      load的部分 我暫時用好了 只是不確定有沒有完成
      我也從你給的網頁裡面的去找save的部分 但看到很多人問了關於save json on android 都沒人去回答 或是回答的不完整(有錯誤) 我沒辦法參考 所以再次向您請教

      突然換成馬力歐的頭像讓我以為是哪位跑來幫您解答 >_<

      刪除
    5. 你可以第一次load從Resources.Load的方式讀取。
      然後之後的讀寫檔案的用Application.persistentDataPath

      這樣應該就沒問題了吧!

      刪除
    6. 所以是先用Resources.Load讀取出來
      儲存到Application.persistentDataPath 之後都從這邊提取對巴
      謝山姆大的提點

      刪除
  7. 迷茫專題學生2018年5月1日 晚上7:31

    您好,我寫了一封信詢問問題,想請您幫我看看

    回覆刪除