关键点:
···C# API (StreamReader\ StreamWriter \ System.Text.Encoding\String.Split)
···Unity API ( JsonUtility.fromjson \jsonUtility.tojson \ WWW )
··UnityAPI 提供的路径参考(常用的):
Application.streamingAssetsPath
Application.persistentDataPath
··· txt格式的json文本文件 , 编码(UTF-8\Unicode\Ascii), Bom字符问题。
______________________________________________________________________________________________
研究背景: 因自己手头正好有一个安卓上的游戏应用,需要读取和写入本地的json文件,例如武器信息/人物属性/得分结算,所以研究了一下。虽然这一块网上的教程都挺多的,但是没找到特别详细和完整,而且针对不同平台会出现不同问题,所以自己研究了很久,也是挖了不少的坑,最后才摸的比较清楚这一块了,跑来写这一篇技术文档。
用的是windows的系统,以及安卓真机,亲测可用。Mac Liunx 和 IOS 还没有测试过,但是照着这个思路做是一样的。
说明一下,该教程没有用到任何插件,包括 读写json的用的也是Unity自带的API。 没有使用jsonLit。 一样很方便好用。
JsonUtility.fromjson<T>(string json) 从字符串类型的json字符信息,转换成 字段 用于构造函数。
JsonUtility.tojson(obj object) 将obj 类型转化成 Json的字符信息 是string类型。
______________________________________________________________________________________________
进入正题:
首先我们要解决在编辑器Editor模式下,window系统环境上 读取和写入本地文件。
这边先介绍两种方法,开头提过。一种是用C#提供的API StreamReader 和 StreamWriter,顾名思义就是读和写,这两个只要给一个字符串类型的路径参数就可以完成工作。(一般能用这两个API读写就尽量用它们, 少用WWW读写)
还有一种使用Unity提供的WWW方法,这个方法只能用来作读取,主要是用在服务器上的,可以从网络上 下载图片 文字 等其他信息,给一个http链接即可, 常用支持协议: ( http:// https:// ftp:// file:// ) 顾名思义最后一个可以用来读本地文件。
关于以上API更详细的信息,推荐翻 官方手册。 针对不同平台,还会有些 小变化和问题,要注意。
以下示例:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//额外引用
using System.IO;
using System.text;
public class blogtest : MonoBehaviour {
//一个测试类
class testJson
{
public string Name;
public testJson(string m_name)
{
Name = m_name;
}
}
List<testJson> jsonList = new List<testJson>();
void Awake()
{
//Editor模式 Window系统
//先用C#的API做读写,需要引入 using system.IO的命名空间。
//该路径是在Project视图下,Asset下的Resources文件夹里。可以往下再写路径。
//这个文件是预先创建的,往里面写几行json信息或其他文本信息。
StreamReader sr = new StreamReader(Application.dataPath + "/Resou/json1.txt");
//临时存储json信息
string jsonInfo;
//可以看下包含的一些常用方法
//包含文件从头到尾的信息,存在一个字符串里。
jsonInfo = sr.ReadToEnd();
//读取每一行的文件信息。
jsonInfo = sr.ReadLine();
//写一个循环体 打印一下。
while((jsonInfo = sr.ReadLine())!=null)
{
//按顺序打印出每行的文本信息 会包含“{ }”json的中括号字符
//这里要注意一个编码问题,如果文本信息是全英文的就没事,如果有包含中文的要使用UTF-8的编码,否则就出现乱码.
Debug.Log(jsonInfo);
//如果json信息里包含的是构造函数的参数,就写在这里。
jsonList.Add(JsonUtility.FromJson<testJson>(jsonInfo));
}
//读完后关闭。
sr.Close();
//接着是写入操作 格式是差不多的。写入通常就是创建,就算没有这个文件名,也会自动创建。
//这里要注意指定编码格式 建议用UTF8支持中文,需要 using System.Text;
StreamWriter sw = new StreamWriter(Application.dataPath + "/Resources/json2.txt",false,Encoding.UTF8);
//用之前存储的testjson类列表信息
for (int i = 0; i < jsonList.Count; i++)
{
//将列表中的每一行信息,转换成json格式然后写入文件。
sw.WriteLine(JsonUtility.ToJson(jsonList[i]));
}
sw.Close()
}
}
以上是在windows系统上,这上面读取和写入都很方便的 而且能直接查看,所以比较简单,移动平台就比较复杂了, 写的较完整,所以字数较多。
——————————————————————————————————————————————
先说明以几个Unity特殊文件夹的作用。这边只提和本章有关的几个,其他的可以在百度了解,比如Editor Plugins。
Reousrces (搭配API : Application.datapath +"/resources/***/***")
刚刚上文用到了Resources文件夹下。
这个文件夹里的所有资源(不管用不用)都会被build打包进apk或其他包里。它的这个文件夹不管放在最外面,还是在其他文件夹下面都会被识别到。 可以清理一些没有用的资源,减少包的大小。
Windows平台可以用这个包进行读写,有个方法是 Resources.Load() 无论在编辑还是运行都可以用,可以加载其中的资源图片 或者模型, 也可以搭配asset bundle用,这个方法很实用 这里不细说。
StreamingAssets (搭配 Application.streamingAssetsPath + "/")
这个文件夹里的所有资源也一样会被build打包进去。但是它不会压缩,资源会占空间。而且这个文件夹最特殊一点是,它只支持读,不支持写。而且在不同平台上它的路径不一样,官方手册可查。
——Mac or Windows
path = Application.dataPath + "/StreamingAssets";
——IOS
path = Application.dataPath + "/Raw";
——Android (注意 )
path = "jar:file://" + Application.dataPath + "!/assets/";
使用以上路径对应平台 ,就能读到这个文件夹里面的东西。
注意,该文件夹 在编译到安卓平台上时,会将资源 压缩成一个压缩包,这种时候就不能使用StreamReader读了,只能用WWW读
还剩下最后一个路径 Application.persistentDataPath 它没有对应的文件夹,应该说默认是不存在的。
这个路径比较特殊,它是只有在程序包安装完毕后,才会创建出来的。所以这个路径下面的文件,只能通过代码 在运行时创建,但是它是支持 可读可写的 还是很方便的 常用来做存档。
它在不同平台的路径:
——Mac
Path = /Users/xxxx/Library/Caches/CompanyName/Product Name
——Windows
Path = C:/Users/xxxx/AppData/LocalLow/CompanyName/ProductName
——IOS
Path = Application/xxxxxxxx/Documents
——Android
Path = /data/data/xxx.xxx.xxx/files 或者是 /Android/data/com.company.xxx/files
注意: 这些路径 最后都是没有带斜杠的,需要自己带上斜杠。
介绍完这些路径后,我们就开始写安卓平台的示例:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//额外引用
using System.IO;
using System.Text;
using System;
public class blogtest : MonoBehaviour {
//一个测试类
class testJson
{
public string Name;
public testJson(string m_name)
{
Name = m_name;
}
}
List<testJson> jsonList = new List<testJson>();
void Start()
{
//现在是用Windows系统环境开发的安卓平台使用WWW读取本地文件 (如果是Mac或者Liunx的开发环境,可能不太一样)
//因为StreamingAssets文件夹会编译成压缩包,不能用StreamReader 只能用WWW读写。
//但是StreamingAssets有限制只能读 不能写。
//所以这里的设计模式是,先把提前写好的静态json文本文件放在StreamingAssets里,用WWW读取后转化成列表,在程序运行时计算。
//在程序最后结算时,写入到另一个路径里, 也就是Application.persistentDataPath 这个路径是只有程序安装后才自动生成的,而且之后会一直存在,只要不删除程序,就可以一直存放在这里。
//首先程序运行时,先判断Application.persistentDataPath该路径下 有没有我们需要的json文件。如果没有就创建。
//声明一个文件信息,包含它的路径。
FileInfo m_file = new FileInfo(Application.persistentDataPath + "/newjson.txt");
//判断 Exists 会返回是否存在
if (!m_file.Exists)
{
//这里是表示不存在 先创建出这个文件
StreamWriter SWfile = m_file.CreateText();
//因为第一次运行程序是肯定不存在这个文件的,会进到这里 需要去读取 StreamingAssets 里的oldjson.txt
//WWW的用法格式 (感觉没有StreamReader好用)
//StreamingAssets的路径格式 建议用下面这个
string url = "jar:file://" + Application.dataPath + "!/assets/" + "oldjson.txt";
//关于这个SkipBom的意义 我放到后面博文里做详解,代码里先照写 这个是巨坑!!
string SkipBom;
string[] jsondata;
//WWW 默认是把文件信息从头到尾全部读取出来,放在下面这个变量里。我们需要对它进行,换行操作 以及把 每一行存在一个数组里。
//WWW 里存放的是一些字节信息 八进制或 十六进制。看文件编码格式
WWW wread = new WWW(url);
//这一句意思是,得到一个UTF8编码的字符串 ,从wread的字节信息里去读,但是跳过前三位字节,从第四位字节去读。 为什么是三位字节,后文解释。
SkipBom = Encoding.UTF8.GetString(wread.bytes, 3, wread.bytes.Length - 3);
//将上面得到的字符串,因为包含了全部json信息,要做一个Split换行,自动转换成数组。 这里有一个换行格式符 是C#里预定义的, {"\r\n"}
jsondata = SkipBom.Split(new string[] { "\r\n" }, StringSplitOptions.None);
//到这里的操作后 就和StreamReader的思路一样了。 使用遍历得到每行的json信息。
foreach (string item in jsondata)
{
jsonList.Add(JsonUtility.FromJson<testJson>(item));
}
}
else
{
//这里表示json文件已存在 第二次启动程序的时候会进这里。
//这时候就可以直接访问persistentDataPath的路径 用StreamReader直接读就好了。
StreamReader sr = new StreamReader(Application.persistentDataPath + "/newjson.txt");
string nextLine;
while ((nextLine = sr.ReadLine()) != null)
{
jsonList.Add(JsonUtility.FromJson<testJson>(nextLine));
}
sr.Close();
}
//接着就是程序运行最后结算的时候写入。 偷懒都写在一个函数里了。
//后面的步骤 就都差不多了, 只要注意路径是对的就好了。
StreamWriter sw = new StreamWriter(Application.persistentDataPath + "/newjson.txt",false,Encoding.UTF8);
for (int i = 0; i < jsonList.Count; i++)
{
sw.WriteLine(JsonUtility.ToJson(jsonList[i]));
}
sw.Close();
}
}
注意1:在Windows和Windows Store应用程序上使用文件协议访问本地文件时,必须指定file:///(带有三个斜杠)。
举例 string url = “file:///” + Application.streamingAssetsPath + "xxx.txt";
注意2:这里讲解SkipBom的意义, 还有被跳过的三个字节。
在Windows环境上,我们一般制作json文件,用notepad++ 或者其他操作 新建一个txt的文本文件。
但是Windows的系统,通常情况下会给txt文件默认开头打上一个Bom字符,Bom字节用记事本是看不到的,它其实是三个 十六进制的字符,如果UTF—16格式通过某些操作会读取到, 它的作用是用来表示这个文件的编码信息, 如果是UTF8编码,就会通过Bom字符去表示,是一种很隐含的信息。
以下引用网上找到的解释:
BOM是“Byte Order Mark”的缩写,用于标记文件的编码。并不是所有的文本编辑工具都能识别BOM标记.在用记事本之类的程序将文本文件保存为UTF-8格式时,记事本会 在文件头前面加上几个不可见的字符(EF BB BF),就是所谓的BOM(Byte Order Mark)。
1) notepad : 可以自动识别出没有带 bom 的 utf-8 编码格式文件,但不可以控制保存文件时是否添加 bom , 如果保存文件,那么会统一添加 bom 。
2)editplus : 不能自动识别出没有 bom 的 utf-8 编码格式文件,保存文件为 utf-8 时会自动添加 bom
3) UltraEdit : 对于字符编码的功能最为强大, 可以自动识别带 bom 和不带 bom 的 utf-8 文件 (可以配置) ; 保存的时候可以通过配置选择是否添加 bom.
一开始没注意到这个问题的时候,我用WWW方法读取到字符串信息,死活转不成json,一直报错说这个json是无用的值。但是我打印出来的 文本信息, 是正确的,格式也正确, 而且和StreamReader读到的对比一下 是一摸一样的。也根本看不到Bom字节,前面也没有空格。但实际它就是存在的,而且Unity是可以识别到的,所以当你的json信息的第一个字符不是“{”中括号时,程序当然是会报错的,没有按照json语句的格式。这个真的坑了我很久,不断的百度,打开了十几个页面,查找了很久,很少有人遇到这个问题大概,没有人写,最后还是被我找到了一篇,来Unity官方 全球的讨论社区, 还是14年12月很早的。但是幸好解决了。
——————————————————————————————————————————
以上就是本节教程的全部内容,用了一个美好的周日,打了很久写完了这篇,我好像话太多了,真的说了很多。
如果有问题,望各位路过大牛一定要指明。说的不对的地方,请帮我挑出来。
还过还有不明白的,可以加我QQ : 593902339 欢迎交流
统一感谢以下技术帖子:
http://blog.csdn.net/musicvs/article/details/49657905 [笨木头Unity3D]杂记003·Unity在Android中读取文件
http://blog.csdn.net/zhaoguanghui2012/article/details/49871775 Unity中C# 文件本地读取,本地保存等实例
https://www.jianshu.com/p/31a41b8f1332 Unity工程 打包到Android平台文件读取路径
http://www.xuanyusong.com/archives/3229 雨松MOMO 2014年09月26日 于 雨松MOMO程序研究院 发表
转载请帮我注明,谢谢!