unity文件存储与读写
程序员文章站
2022-05-17 15:47:23
...
最近做了一个小项目,遇到了一个问题,感觉比较经典,就想写下来,希望对其他人有所帮助
问题的报错就是这个.....
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备份
以上都是理论部分......废话不多说 来点干货
这里分别是五个模式下的核心代码运行之后的效果如图
第二张图则是在手机上运行结果
我把工程项目文件也传上来了,有兴趣可以下载看看
问题的报错就是这个.....
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; }
这里分别是五个模式下的核心代码运行之后的效果如图
第二张图则是在手机上运行结果
我把工程项目文件也传上来了,有兴趣可以下载看看
上一篇: RadRails 1.0正式版发布
下一篇: 第一次配置EasyAR的HelloAR
推荐阅读
-
Android编程中File文件常见存储与读取操作demo示例
-
Android编程中File文件常见存储与读取操作demo示例
-
Mysql单文件存储删除数据文件容量不会减少的bug与解决方法
-
iOS应用中存储用户设置的plist文件的创建与读写教程
-
Mysql单文件存储删除数据文件容量不会减少的bug与解决方法
-
iOS应用中存储用户设置的plist文件的创建与读写教程
-
python文件读写操作与linux shell变量命令交互执行的方法
-
Python实现爬虫抓取与读写、追加到excel文件操作示例
-
php中读写文件与读写数据库的效率比较分享
-
linux 可执行文件与写操作的同步问题(文件读写操作产生的锁机制)