unity文件存储与读写

最近做了一个小项目,遇到了一个问题,感觉比较经典,就想写下来,希望对其他人有所帮助
问题的报错就是这个.....
I/Unity   ( 8298): NullReferenceException: Object reference not set to an instan
ce of an object
I/Unity   ( 8298):   at Heathwork.Update () [0x00000] in <filename unknown>:0
Heathwork是一个挂载在gameobject上的脚本组件,在unity上测试一直都是好的,但是在手机上就是不行
一直报这个错,经过很多查询才知道,原来是我在untiy上使用了一个配置文件放在/assets/data下面,unity打包的时候
没有打进去,所以手机上是读不到这个文件的,我在Heathwork之中的start之中使用了这个文件,于是Heathwork没有实例化成功,
所以在update的时候才会报未将对象引用到对象的实例。于是我又去查了一下unity的打包机制,在此总结一下
1.unity(5.x)的资源打包过程
1.1 唯一的API
调用builgpipeline.BuildAssetBundles,引擎将自动根据资源的assetbundleName
属性批量打包,自动建立bundle以及资源之间的依赖关系
1.2 打包规则
在资源的inpector界面最下方可设置一个abName,每个abName(包含路劲)对应一个
bundle,即abName相同的资源会打在一个Bundle中,如果所以来的资源设置了不同的abName
则会与之建立依赖关系,避免出现冗余,
支持增量式发布,即在资源内容改变并重新打包时,会自动跳过内容未变的bundle。
1.3 新打包的选项
5.x 下默认开启三个选项(completeAssets,用于保证资源单的完备性,collectDependencies
,用于收集资源的依赖项,DeterministicAssetBundle,用于为资源维护固定id)之外
ForceRebuildAssetBundle
用于强制重打所有AssetBundle文件;
IgnoreTypeTreeChanges
用于判断AssetBundle更新时,是否忽略TypeTree的变化;
AppendHashToAssetBundleName
用于将Hash值添加在AssetBundle文件名之后,开启这个选项,可以直接通过文件名来判断哪些Bundle的内容进行了更新
(4.x下普遍需要通过比较二进制等方法来判断,但在某些情况下即使内容不变重新打包,Bundle的二进制也会变化)。

1.4 Manifest文件
在打包后生成的文件夹中,每个Bundle都会对应一个manifest文件,记录了Bundle的一些信息,
但这类manifest只在增量式打包时才用到;同时,根目录下还会生成一个同名manifest文件及其对应的Bundle文件,
通过该Bundle可以在运行时得到一个AssetbundleManifest对象,而所有的Bundle以及各自依赖的Bundle都可以通过该对象提供的接口进行获取。

1.5 Variant参数
在资源的Inspector界面最下方,除了可以指定abName,在其后方还可以指定Variant。打包时,Variant会作为
后缀添加在Bundle名字之后。相同abName,不同variant的Bundle中,资源必须是一一对应的,且他们在Bundle中的ID也是相同的,
从而可以起到相互替换的作用。
https://blog.uwa4d.com/archives/ABtopic_2.html
2.unity项目下的文件结构
2.1 GUID与fileID(本地ID)
   Unity会为每个导入到Assets目录中的资源创建一个meta文件,文件中记录了GUID,GUID用来记录资源之间的引用关系。
还有fileID(本地ID),用于标识资源内部的资源。资源间的依赖关系通过GUID来确定;资源内部的依赖关系使用fileID来确定。
2.2 MonoScripts
        一个MonoScripts含有三个字符串:程序库名称,类名称,命名空间。
构建工程时,Unity会收集Assets文件夹中独立的脚本文件并编译他们,组成一个Mono程序库。Unity会将Assets目录中的语言分开编译,Assets/Plugins目录中的脚本同理。Plugin子目录之外的C#脚本会放在Assembly-CSharp.dll中。而Plugin及其子目录中的脚本则放置在Assembly-CSharp-firstpass.all中。
这些程序库会被MonoScripts所引用,并在程序第一次启动时被加载。

2.3 Assets
为unity编辑器下的资源文件夹,unity编辑时的所有资源都将置于此文件夹内,
可以使用AssetDatabase.LoadAssetAtPath("Assets/x.txt"); 来获取资源对象
ps:只能在编辑器下使用,当项目打包后,游戏内无法运作,参数为包含Assets的文件全路径
并且需要文件后缀
Assets下的资源除特殊文件夹内 或者会被打入保内的场景引用的资源化,其余资源不会被打入包中,
2.4 Resources
资源载入
        Assets下的特殊文件夹,此文件夹内的资源将会在项目打包时,全部打入包内,并能通过以下方法获得对象:
Resources.Load("fileName");

注意:函数内的参数为相对于Resource目录下的文件路径与名称,不包含后缀。Assets目录下可以拥有任意路径及数量的Resources文件夹,在运行时,Resources下的文件路径将被合并。
例:Assets/Resources/test.txt与 Assets/TestFloder/Resources/test.png在使用Resource.Load("test")载入时,将被视为同一资源,只会返回第一个符合名称的对象。如果使用Resource.Load(“test”)将返回text.txt;
如果在Resources下有相同路径及名称的资源,使用以上方法只能获得第一个符合查找条件的对象,使用以下方法能或得到所有符合条件的对象:
Object[] assets = Resources.LoadAll("fileName");
TextAsset[] assets = Resources.LoadAll("fileName");
在工程进行打包后,Resource文件夹中的资源将进行加密与压缩,打包后的程序内将不存在Resource文件夹,故无法通过路径访问以及更新资源
意思就是打包无法进行更改了
2.5 StreamingAssets
概述
        StreamingAssets文件夹为流媒体文件夹,此文件夹内的资源将不会经过压缩与加密,原封不动的打包进游戏包内。在游戏安装时,StreamAssets文件件内的资源将根据平台,移动到对应的文件夹内。StreamingAssets文件夹在Android与IOS平台上为只读文件夹.
    你可以使用以下函数获得不同平台下的StreamingAssets文件夹路径:
Application.streamingAssetsPath
    请参考以下各平台下StreamingAssets文件夹的等价路径,Application.dataPath为程序安装路径。Android平台下的路径比较特殊,请留意此路径的前缀,在一些资源读取的方法中是不必要的(AssetBundle.LoadFromFile,下详)
Application.dataPath+"/StreamingAssets"//Windows OR MacOS
Application.dataPath+"/Raw" //IOS
"jar:file://"+Application.dataPath+"!/assets/" //Android
在pc跟mac中可以有读写权限,但是移动端只支持读取操作
ps:也可以在Assets下直接创建其他文件夹  也会被打进项目中,使用application.datapath来访问,但是
在移动端是没有访问权限的!!!!

2.6 AssetBundle
概述
        AssetBundles let you stream additional assets via the WWW class and instantiate them at runtime. AssetBundles are created via BuildPipeline.BuildAssetBundle.
AssetBundle是Unity支持的一种文件储存格式,也是Unity官方推荐的资源存储与更新方式,它可以对资源(Asset)进行压缩,分组打包,动态加载,以及实现热更新,
但是AssetBundle无法对Unity脚本进行热更新,因为其需要在打包时进行编译。
它的位置就在 你随便点击一个perfab 在inspector中 最下面的那个就是assetBundle
总结几点:
1.Resources 打包会被进入,但是会被加密,不能进行更改,可存放一些固定配置的文件,通过
Resources.Load("fileName"); 或者使用textAsset拖入脚本中使用
2.直接在Assets中放入文件
使用application.dataPath+"fileName" 来读取  但是这个在移动端是没有访问权限的
3.StreamingAssets
在项目中创建这个文件夹之后,
可以使用Application.streamingAssetsPath 来读取文件 也可以使用Application.dataPath来读取
Application.dataPath使用的话必须分清楚平台路径
#if UNITY_ANDROID   //安卓 
string filepath= "jar:file://" + Application.dataPath + "!/assets/"+"Filename"; 
#elif UNITY_IPHONE  //iPhone 
string filepath=Application.dataPath + "/Raw/"+"Filename"; 
#elif UNITY_STANDALONE_WIN || UNITY_EDITOR  //windows平台和web平台 
string filepath = "file://" + Application.dataPath + "/StreamingAssets/"+"Filename"; 
#end
实验做出来的是只能www去读  IO无法读取内容 在手机上
4.使用Application.persistentDataPath 来操作文件(推荐)
此文件存在手机沙盒中,项目中是找不到的,
在移动端使用服务器下载文件后 保存在这个文件 使用md5对比下载更新新资源
如果没有服务器 可以通过文件流的形式在本地读取然后写入Application.persistentDataPath中,
再通过Application.persistentDataPath 来读取操作文件
ps:在移动端可以进行任意文件操作,同时IOS会被icloud备份

5.Application.temporaryCachePath
操作方式跟Application.persistentDataPath一样  但是不能被icloud备份


以上都是理论部分......废话不多说 来点干货
void Start () {
		text = (TextAsset)Resources.Load ("test2");
		testtxt = text.text;
		this.gameObject.GetComponent<Text> ().text = testtxt;
	}

	testtxt = text.text;
		this.gameObject.GetComponent<Text> ().text = testtxt;

public TextAsset text;
	public string testtxt;
	// Use this for initialization
	void Start () {

		//string path = Application.streamingAssetsPath+"/test3.txt";
		//或者使用下面的filepath 也是可以的
		#if UNITY_ANDROID   //安卓  
			string filepath= "jar:file://" + Application.dataPath + "!/assets/"+"/test3.txt";  
		#elif UNITY_IPHONE  //iPhone  
			string filepath=Application.dataPath + "/Raw/"+"/test3.txt";  
		#elif UNITY_STANDALONE_WIN || UNITY_EDITOR  //windows平台和web平台  
			string filepath = "file://" + Application.dataPath + "/StreamingAssets/"+"/test3.txt";  
		#endif 

		testtxt = Loadfile(filepath);
		this.gameObject.GetComponent<Text> ().text = testtxt+"  "+filepath;
	}
	
	// Update is called once per frame
	void Update () {
		
	}
	public string Loadfile(string path){
		StreamReader sr = null;
		try{
			sr = File.OpenText(path);

		}catch(Exception e){
			return null;

		}
		string line;
		//字符分割就不做了 可以用sr.ReadLine();
		line = sr.ReadToEnd ();
		sr.Close ();
		sr.Dispose ();
		return line;
	}


public TextAsset text;
	public string testtxt;
	// Use this for initialization
	void Start () {
		#if UNITY_ANDROID   //安卓  
			string path = Application.streamingAssetsPath+"/test4.txt";
		#elif UNITY_STANDALONE_WIN || UNITY_EDITOR  //windows平台和web平台  
			string path ="file://"+ Application.streamingAssetsPath+"/test4.txt";
		#endif

		StartCoroutine (readwww (path));
	}
	
	// Update is called once per frame
	void Update () {
		
	}
	IEnumerator readwww(string path){
		//Debug.Log (path);
		WWW www = new WWW (path);
		yield return www;
		testtxt = www.text;
		//Debug.Log ("!!!"+testtxt);
		this.gameObject.GetComponent<Text> ().text = testtxt+path;
	}
	public string Loadfile(string path){
		StreamReader sr = null;
		try{
			sr = File.OpenText(path);

		}catch(Exception e){
			return null;

		}
		string line;
		//字符分割就不做了 可以用sr.ReadLine();
		line = sr.ReadToEnd ();
		sr.Close ();
		sr.Dispose ();
		return line;
	}

public TextAsset text;
	public string testtxt;
	// Use this for initialization
	void Start () {
		string txt = "测试手机沙盒的读与写";
		string path = Application.persistentDataPath+"/test5.txt";
		write (path, txt);
		testtxt = Loadfile (path);
		this.gameObject.GetComponent<Text> ().text = testtxt+path;
	}
	
	// Update is called once per frame
	void Update () {
		
	}
	public void write(string path,string text){
		StreamWriter sr = null;
		FileInfo file = new FileInfo (path);
		if (!file.Exists) {
			sr = file.CreateText ();
		}else{
			sr = file.AppendText ();
		}
		sr.Write (text);
		sr.Close ();
		sr.Dispose ();
	}
	public string Loadfile(string path){
		StreamReader sr = null;
		try{
			sr = File.OpenText(path);

		}catch(Exception e){
			return null;

		}
		string line;
		//字符分割就不做了 可以用sr.ReadLine();
		line = sr.ReadToEnd ();
		sr.Close ();
		sr.Dispose ();
		return line;
	}

这里分别是五个模式下的核心代码运行之后的效果如图
第二张图则是在手机上运行结果
我把工程项目文件也传上来了,有兴趣可以下载看看

猜你喜欢

转载自ligang7895123.iteye.com/blog/2378231