推荐阅读:
- 我的CSDN
- 我的博客园
- QQ群:704621321
- 我的个人博客
tolua
的GitHub
下载地址:https://github.com/topameng/tolua
假设我们下载的是=LuaFramework_UGUI=,它是基于Unity 5.0 + UGUI + tolua
构建的工程
下载下来得到一个=LuaFramework_UGUI-master.zip=
解压之后就是一个Unity
的工程,直接用Unity
打开,首次打开工程会询问生成注册文件,点击确定即可
首先要执行lua
资源的生成(打AssetBundle
),点击菜单【LuaFramework】-【Build Windows Resource】
会把lua
代码打成AssetBundle
放在StreamingAssets
中。
如果你用的不是Unity5.x
,而是Unity2020
,那么可能会报错:
这是因为新版本的Unity
有些属性和接口已经废弃了的原因,我们需要特殊处理一下
一个是Light
类,一个是QualitySettings
类,这两个类我们一般不需要在lua
中使用,所以我们不对他们生产Wrap
即可:
CustomSettings.cs
,把 _GT(typeof(Light)),
和 _GT(typeof(QualitySettings)),
这两行注释掉【Lua】-【Clear wrap files】
清理掉Wrap
【Lua】-【Generate All】
重新生成Wrap
,【LuaFramework】-【Build Windows Resource】
生成lua
资源。执行【Lua】-【Generate All】
菜单的时候,你可能会报错
定位到报错的位置
添加判空
重新执行【Lua】-【Generate All】
菜单
生成后应该还有报错
这是因为新版的ParticleSystem
类新增了一些接口,我们可以定位到对应报错的地方,把报错的地方注释掉。
不过为了防止下次执行【Lua】-【Generate All】
菜单时又被覆盖导致报错,我们可以把UnityEngine_ParticleSystemWrap.cs
移动到BaseType
目录中
并把CustomSettings.cs
中的_GT(typeof(ParticleSystem)),
注释掉,并在LuaState.cs
注册ParticleSystemWrap类
同理,UnityEngine_MeshRendererWrap.cs
可能也会报错,按上面的处理方式处理。
最后,【LuaFramework】-【Build Windows Resource】
成功生成AssetBundle
,我们可以在StreamingAssets
中看到很多AssetBundle
文件。
接下来,我们就可以运行Demo
场景了。打开main
场景
运行效果
如果你不想手动修复上报的报错,我将修复好的版本上传到了GitHub
,使用Unity2020
可以直接运行。
GitHub
工程地址:https://gitee.com/shirln/Unity2020-LuaFramework-UGUI
可以使用subline
,也可以使用visual studio
,个人偏好使用visual studio
,配合插件BabeLua
Unity写lua代码的vs插件:BabeLua: https://blog.csdn.net/linxinfa/article/details/88191485
上面这个Lua
动态创建出来的面板的控制逻辑在PromptCtrl.lua
脚本中,我们可以看到lua
工程中使用了经典的MVC
框架。
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
所有的controler
在CtrlManager
中注册
-- CtrlManager.lua
function CtrlManager.Init()
logWarn("CtrlManager.Init----->>>");
ctrlList[CtrlNames.Prompt] = PromptCtrl.New();
ctrlList[CtrlNames.Message] = MessageCtrl.New();
return this;
end
通过CtrlManager
获取对应的controler
对象,调用Awake()
方法
-- CtrlManager.lua
local ctrl = CtrlManager.GetCtrl(CtrlNames.Prompt);
if ctrl ~= nil then
ctrl:Awake();
end
controler
类中,Awake()
方法中调用C#
的PanelManager
的CreatePanel
方法
-- PromptCtrl.lua
function PromptCtrl.Awake()
logWarn("PromptCtrl.Awake--->>");
panelMgr:CreatePanel('Prompt', this.OnCreate);
end
C#
的PanelManager
的CreatePanel
方法去加载界面预设,并挂上LuaBehaviour
脚本
这个LuaBehaviour
脚本,主要是管理panel
的生命周期,调用lua
中panel
的Awake
,获取UI
元素对象
-- PromptPanel.lua
local transform;
local gameObject;
PromptPanel = {};
local this = PromptPanel;
--启动事件--
function PromptPanel.Awake(obj)
gameObject = obj;
transform = obj.transform;
this.InitPanel();
logWarn("Awake lua--->>"..gameObject.name);
end
--初始化面板--
function PromptPanel.InitPanel()
this.btnOpen = transform:Find("Open").gameObject;
this.gridParent = transform:Find('ScrollView/Grid');
end
--单击事件--
function PromptPanel.OnDestroy()
logWarn("OnDestroy---->>>");
end
panel
的Awake
执行完毕后,就会执行controler
的OnCreate()
,在controler
中对UI
元素对象添加一些事件和控制
-- PromptCtrl.lua
--启动事件--
function PromptCtrl.OnCreate(obj)
gameObject = obj;
transform = obj.transform;
panel = transform:GetComponent('UIPanel');
prompt = transform:GetComponent('LuaBehaviour');
logWarn("Start lua--->>"..gameObject.name);
prompt:AddClick(PromptPanel.btnOpen, this.OnClick);
resMgr:LoadPrefab('prompt', { 'PromptItem' }, this.InitPanel);
end
AppFacade.Instance.StartUp();
这个接口会抛出一个=NotiConst.START_UP=事件,对应的响应类是=StartUpCommand=
using UnityEngine;
using System.Collections;
using LuaFramework;
public class StartUpCommand : ControllerCommand {
public override void Execute(IMessage message) {
if (!Util.CheckEnvironment()) return;
GameObject gameMgr = GameObject.Find("GlobalGenerator");
if (gameMgr != null) {
AppView appView = gameMgr.AddComponent();
}
AppFacade.Instance.RegisterCommand(NotiConst.DISPATCH_MESSAGE, typeof(SocketCommand));
AppFacade.Instance.AddManager(ManagerName.Lua);
AppFacade.Instance.AddManager(ManagerName.Panel);
AppFacade.Instance.AddManager(ManagerName.Sound);
AppFacade.Instance.AddManager(ManagerName.Timer);
AppFacade.Instance.AddManager(ManagerName.Network);
AppFacade.Instance.AddManager(ManagerName.Resource);
AppFacade.Instance.AddManager(ManagerName.Thread);
AppFacade.Instance.AddManager(ManagerName.ObjectPool);
AppFacade.Instance.AddManager(ManagerName.Game);
}
}
这里初始化了各种管理器,我们可以根据具体需求进行改造和自定义。
LuaManager
这个管理器是必须的,掌管整个lua
虚拟机的生命周期。它主要是加载lua
库,加载lua
脚本,启动lua
虚拟机,执行Main.lua
。
AppConst
定义了一些常量。
其中=AppConst.LuaBundleMode=是lua
代码AssetBundle
模式。它会被赋值给=LuaLoader=的beZip
变量,在加载lua
代码的时候,会根据beZip
的值去读取lua
文件,false
则去search path
中读取lua
文件,否则从外部设置过来的bundle
文件中读取lua
文件。默认为true
。=在Editor环境下,建议把AppConst.LuaBundleMode设为false,这样方便运行,否则写完lua代码需要生成AssetBundle才可以运行到。=
#if UNITY_EDITOR
public const bool LuaBundleMode = false;
#else
public const bool LuaBundleMode = true;
#endif
=LuaLoader=和=LuaResLoader=都继承=LuaFileUtils=。lua
代码会先从=LuaFramework.Util.AppContentPath=目录解压到=LuaFramework.Util.DataPath=目录中,lua
文件列表信息记录在files.txt
中,此文件也会拷贝过去。然后从=LuaFramework.Util.DataPath=目录中读取lua
代码。
public static string AppContentPath() {
string path = string.Empty;
switch (Application.platform) {
case RuntimePlatform.Android:
path = "jar:file://" + Application.dataPath + "!/assets/";
break;
case RuntimePlatform.IPhonePlayer:
path = Application.dataPath + "/Raw/";
break;
default:
path = Application.dataPath + "/" + AppConst.AssetDir + "/";
break;
}
return path;
}
public static string DataPath {
get {
string game = AppConst.AppName.ToLower();
if (Application.isMobilePlatform) {
return Application.persistentDataPath + "/" + game + "/";
}
if (AppConst.DebugMode) {
return Application.dataPath + "/" + AppConst.AssetDir + "/";
}
if (Application.platform == RuntimePlatform.OSXEditor) {
int i = Application.dataPath.LastIndexOf('/');
return Application.dataPath.Substring(0, i + 1) + game + "/";
}
return "c:/" + game + "/";
}
}
完了之后,再进行远程的更新检测,看看用不用热更lua
代码,远程url
就是=AppConst.WebUrl=,先下载files.txt
,然后再读取lua
文件列表进行下载。
启动框架后,会创建GameManager
游戏管理器,它负责检测lua
逻辑代码的更新检测和加载(Main.lua
是在LuaManager
中执行的),我们可以在GameManager
中DoFile
我们自定义的lua
脚本,比如Game.lua
脚本。
GameManager
可以获取到LuaManager
对象,通过=LuaManager.CallFunction=接口调用。
也可以用=Util.CallMethod=接口调用,两个接口的参数有差异,需要注意。
public object[] CallFunction(string funcName, params object[] args) {
LuaFunction func = lua.GetFunction(funcName);
if (func != null) {
return func.LazyCall(args);
}
return null;
}
public static object[] CallMethod(string module, string func, params object[] args) {
LuaManager luaMgr = AppFacade.Instance.GetManager(ManagerName.Lua);
if (luaMgr == null) return null;
return luaMgr.CallFunction(module + "." + func, args);
}
假设现在我们有一个C#
类
using UnityEngine;
public class MyTest : MonoBehaviour
{
public int myNum;
public void SayHello()
{
Debug.Log("Hello,I am MyTest,myNum: " + myNum);
}
public static void StaticFuncTest()
{
Debug.Log("I am StaticFuncTest");
}
}
我们想在lua
中访问这个MyTest
类的函数。首先,我们需要在=CustomSettings.cs=中的customTypeList
数组中添加类的注册:
_GT(typeof(MyTest)),
然后然后再单击菜单【Lua】-【Generate All】
生成Wrap
,生成完我们会看到一个=MyTestWrap=类
接下来就可以在lua中访问了。=(注意AppConst.LuaBundleMode的值要设为false,方便Editor环境下运行lua代码,否则需要先生成AssetBundle才能运行)=
function Game.TestFunc()
-- 静态方法访问
MyTest.StaticFuncTest()
local go = UnityEngine.GameObject("go")
local myTest = go:AddComponent(typeof(MyTest))
-- 成员变量
myTest.myNum = 5
-- 成员方法
myTest:SayHello()
end
调用Game.TestFunc()
=注意,静态方法、静态变量、成员变量、成员属性使用 “.” 来访问,比如上面的 myTest.myNum,成员函数使用 “:” 来访问,比如上面的 myTest:SayHello()=
function fib(n)
local a, b = 0, 1
while n > 0 do
a, b = b, a + b
n = n - 1
end
return a
end
function CoFunc()
print('Coroutine started')
for i = 0, 10, 1 do
print(fib(i))
coroutine.wait(0.1)
end
print("current frameCount: "..Time.frameCount)
coroutine.step()
print("yield frameCount: "..Time.frameCount)
local www = UnityEngine.WWW("http://www.baidu.com")
coroutine.www(www)
local s = tolua.tolstring(www.bytes)
print(s:sub(1, 128))
print('Coroutine ended')
end
调用
coroutine.start(CoFunc)
local co = coroutine.start(CoFunc)
coroutine.stop(co)
假设现在有这么一份json文件
{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Mark up Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML"]
},
"GlossSee": "markup"
}
}
}
}
}
假设我们已经把上面的json文件的内容保存到变量jsonStr字符串中,现在在lua中要解析它
local json = require 'cjson'
function Test(str)
local data = json.decode(str)
print(data.glossary.title)
s = json.encode(data)
print(s)
end
System.Action cb = (s) => { Debug.Log(s); };
Util.CallMethod("Game", "TestCallBackFunc", cb);
-- lua调用C#的托管
function Game.TestCallBackFunc(cb)
if nil ~= cb then
System.Delegate.DynamicInvoke(cb,"Hello, I am lua, I call Delegate")
end
end
有时候,我们没有把我们的C#
类生成Wrap
,但是又需要在lua
中调用,这个时候,可以通过反射来调用。
假设我们有一个C#
类:MyClass
public sealed class MyClass
{
public string myName;
public int myAge { get; set; }
public static void SayHello()
{
Debug.Log("Hello, I am MyClass's static func: SayHello");
}
public void SayNum(int n)
{
Debug.Log("SayNum: " + n);
}
public void SayInfo()
{
Debug.Log("SayInfo, myName: " + myName + ",myAge: " + myAge);
}
}
在lua
中
-- Game.lua
function Game.TestReflection()
require 'tolua.reflection'
tolua.loadassembly('Assembly-CSharp')
local BindingFlags = require 'System.Reflection.BindingFlags'
local t = typeof('MyClass')
-- 调用静态方法
local func = tolua.getmethod(t, 'SayHello')
func:Call()
func:Destroy()
func = nil
-- 实例化
local obj = tolua.createinstance(t)
-- 字段
local field = tolua.getfield(t, 'myName')
-- 字段Set
field:Set(obj, "linxinfa")
-- 字段Get
print('myName: ' .. field:Get(obj))
field:Destroy()
-- 属性
local property = tolua.getproperty(t, 'myAge')
-- 属性Set
property:Set(obj, 29, null)
-- 属性Get
print('myAge: ' .. property:Get(obj, null))
property:Destroy()
--public成员方法SayNum
func = tolua.getmethod(t, 'SayNum', typeof('System.Int32'))
func:Call(obj, 666)
func:Destroy()
--public成员方法SayInfo
func = tolua.getmethod(t, 'SayInfo')
func:Call(obj)
func:Destroy()
end
nil
是lua
对象的空,null
表示c#
对象的空。假设我们在c#
中有一个GameObject
对象传递给了lua
的对象a
,接下来我们把这个GameObject
对象Destroy
了,并在c#
中把这个GameObject
对象赋值为null
,此时lua
中的对象a
并不会等于nil
=如果要在lua
中判断一个对象是否为空,安全的做法是同时判断nil
和null
=
-- lua中对象判空
function IsNilOrNull(o)
return nil == o or null == o
end
-- 1是周日,2是周一,以此类推
function GetTodayWeek()
local t = os.date("*t", math.floor(os.time()))
return t.wday
end
方法一
function GetTodayYMD()
local t = os.date("*t", math.floor(os.time()))
return t.year .. "/" .. t.month .. "/" .. t.day
end
方法二
function GetTodayYMD()
-- 如果要显示时分秒,则用"%H:%M:%S"
return os.date("%Y/%m%d", math.floor(os.time()))
end
-- 参数str是你的字符串,比如"小明|小红|小刚"
-- 参数sep是分隔符,比如"|"
-- 返回值为{"小明","小红","小刚"}
function SplitString(str, sep)
local sep = sep or " "
local result = {}
local pattern = string.format("([^%s]+)", sep)
string.gsub(s, pattern, function(c) result[#result + 1] = c end)
return result
end
-- 参数num是数字,如3428439,转换结果"3,428,439"
function FormatNumStrWithComma(num)
local numstr = tostring(num)
local strlen = string.len(numstr)
local splitStrArr = {}
for i = strlen, 1, -3 do
local beginIndex = (i - 2 >= 1) and (i - 2) or 1
table.insert(splitStrArr, string.sub(numstr, beginIndex, i))
end
local cnt = #splitStrArr
local result = ""
for i = cnt, 1, -1 do
if i == cnt then
result = result .. splitStrArr[i]
else
result = result .. "," .. splitStrArr[i]
end
end
return result
end
-- 缓存
local name2Type = {}
-- 参数gameObject物体对象
-- 参数componentName,组件名字,字符串
function AddComponent(gameObject, componentName)
local component = gameObject:GetComponent(componentName)
if nil ~= component then return component end
local componentType = name2Type[componentName]
if nil == componentType then
componentType = System.Type.GetType(componentName)
if nil == componentType then
print("AddComponent Error: " .. componentName)
return nil
else
name2Type[componentName] = componentType
end
end
return gameObject:AddComponent(componentType)
end
lua中的table是引用类型,有时候我们为了不破坏原有的table,可能要用到深拷贝
function DeepCopy(t)
if nil == t then return nil end
local result = ()
for k, v in pairs(t) do
if "table" == type(v) then
result[k] = DeepCopy(v)
else
result[k] = v
end
end
return result
end
function Round(fnum)
return math.floor(fnum + 0.5)
end
-- 需要把C#的System.Text.RegularExpressions.Regex生成Wrap类
function CheckIfStrContainChinese(str)
return System.Text.RegularExpressions.Regex.IsMatch(str, "[\\u4e00-\\u9fa5]")
end
-- 通过索引获取数字的某一位,index从1开始
function GetBitByIndex(num, index)
if nil == index then
print("LuaUtil.GetBitByIndex Error, nil == index")
return 0
end
local b = bit32.lshift(1,(index - 1))
if nil == b then
print("LuaUtil.GetBitByIndex Error, nil == b")
return 0
end
return bit32.band(num, b)
end
-- 设置数字的某个位为某个值,num:目标数字,index:第几位,从1开始,v:要设置成的值,0或1
function SetBitByIndex(num, index, v)
local b = bit32.lshift(1,(index - 1))
if v > 0 then
num = bit32.bor(num, b)
else
b = bit32.bnot(b)
num = bit32.band(num, b)
end
return num
end
有时候,字符串过长需要截断显示,比如有一个昵称叫“我的名字特别长一行显示不下”,需求上限制最多显示5个字,超过的部分以…替代,即"我的名字特…"。首先要计算含有中文的字符串长度,然后再进行截断
-- 含有中文的字符串长度
function StrRealLen(str)
if str == nil then return 0 end
local count = 0
local i = 1
while (i < #str) do
local curByte = string.byte(str, i)
local byteCount = 1
if curByte >= 0 and curByte <= 127 then
byteCount = 1
elseif curByte >= 192 and curByte <= 223 then
byteCount = 2
elseif curByte >= 224 and curByte <= 239 then
byteCount = 3
elseif curByte >= 240 and curByte <= 247 then
byteCount = 4
end
local char = string.sub(str, i, i + byteCount - 1)
i = i + byteCount
count = count + 1
end
return count
end
-- 限制字符长度(多少个字)
-- 参数str,为字符串
-- 参数limit为限制的字数,如8
-- 参数extra为当超过字数时,在尾部显示的字符串,比如"..."
function LimitedStr(str, limit, extra)
limit = limit or 8
extra = extra or ""
local text = ""
-- 含有中文的字符串长度
if StrRealLen(str) > limit then
text = LuaUtil.sub_chars(str, limit) .. "..." .. extra
else
text = str .. extra
end
return text
end
-- 判断字符串str是否是以某个字符串start开头
function StringStartsWith(str, start)
return string.sub(str, 1, string.len(start)) == start
end
打app
整包的时候,备份一份lua
全量文件,后面打lua
增量包的时候,根据文件差异进行比对,新增和差异的lua文件打成一个lua_update.bundle
,放在一个update
文件夹中,并压缩成zip
,放到服务器端,客户端通过https
下载增量包并解压到Application.persistentDataPath
目录。游戏加载lua
文件的时候,优先从update
文件夹中的lua_update.bundle
中查找lua
脚本。
做个编辑器工具,指定某个或某些资源文件(预设、音频、动画、材质等),打成多个assetbundle
,放在一个update
文件夹中,并压缩成一个zip
,放到服务器端,客户端通过https
下载增量包并解压到Application.persistentDataPath
目录。
游戏加载资源文件的时候,优先从update
文件夹中查找对应的资源文件。
persistentDataPath/res/
├──/update/
│ ├──/lua/
│ │ └──lua_update.bundle #lua增量bundle
│ ├──/res/
│ │ ├──aaa.bundle #预设aaa的bundle
│ │ ├──bbb.bundle #音频bbb的bundle
│ │ └──... #其他各种格式的资源bundle
│ └──/cfg/
│ ├──cfg.bundle #配置增量bundle
│ └──... #其他文本或二进制文件增量bundle
├──out_put.log #游戏日志
└──...
Unity的Application有几个关键的Path:
Application.dataPath
Application.streamingAssetsPath
Application.persistentDataPath
Application.temporaryCachePath
在个平台下的具体路径如下:
Application.dataPath | Application.streamingAssetsPath | Application.persistentDataPath | **Application.temporaryCachePath ** | |
---|---|---|---|---|
iOS | Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxx.app/Data | Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxx.app/Data/Raw | Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Documents | Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Library/Caches |
Android | /data/app/xxx.xxx.xxx.apk | jar:file:///data/app/xxx.xxx.xxx.apk/!/assets | /data/data/xxx.xxx.xxx/files | /data/data/xxx.xxx.xxx/cache |
Windows | /Assets | /Assets/StreamingAssets | C:/Users/xxxx/AppData/LocalLow/CompanyName/ProductName | C:/Users/xxxx/AppData/Local/Temp/CompanyName/ProductName |
Mac | /Assets | /Assets/StreamingAssets | /Users/xxxx/Library/Caches/CompanyName/Product Name | /var/folders/57/6b4_9w8113x2fsmzx_yhrhvh0000gn/T/CompanyName/Product Name |
Windows Web Player | file:///D:/MyGame/WebPlayer (即导包后保存的文件夹,html文件所在文件夹) |
Resources文件夹是一个只读的文件夹,通过Resources.Load()来读取对象。因为这个文件夹下的所有资源都可以运行时来加载,所以Resources文件夹下的所有东西都会被无条件的打到发布包中。建议这个文件夹下只放Prefab或者一些Object对象,因为Prefab会自动过滤掉对象上不需要的资源。举个例子我把模型文件还有贴图文件都放在了Resources文件夹下,但是我有两张贴图是没有在模型上用的,那么此时这两张没用的贴图也会被打包到发布包中。假如这里我用Prefab,那么Prefab会自动过滤掉这两张不被用的贴图,这样发布包就会小一些了。
StreamingAssets文件夹也是一个只读的文件夹,但是它和Resources有点区别,Resources文件夹下的资源会进行一次压缩,而且也会加密,不使用点特殊办法是拿不到原始资源的。但是StreamingAssets文件夹就下面的所有资源不会被加密,是原封不动的打包到发布包中,这样很容易就拿到里面的文件。所以StreamingAssets适合放一些二进制文件,而Resources更适合放一些GameObject和Object文件。 在移动平台,StreamingAssets只能用过www类来读取!!
StreamingAssets在不同的平台上面 (Windows、iOS、Android),该目录最终发布的位置不同,所以读取的方法也不同。
windows | iOS | Android | |
---|---|---|---|
IO读取路径 | Application.StreamingAssetPath+"/myfile.txt" | Application.StreamingAssetPath+"/myfile.txt" | 不支持 |
WWW读取路径 | “file://”+Application.StreamingAssetPath+"/myfile.txt" | “file://”+Application.StreamingAssetPath+"/myfile.txt" | Application.StreamingAssetPath+"/myfile.txt" |
WWW是异步加载所以执行加载命令式不能直接执行读取解析操作,要等待
WWW www = new WWW(filePath);
Android之所以不支持C# IO流 方式读取StreamingAssets下的文件,是因为Android手机中,StreamingAssets下的文件都包含在压缩的.jar文件中(这基本上与标准的zip压缩文件的格式相同)。不能直接用读取文件的函数去读,而要用WWW方式。具体做法如下:
1.把你要读取的文件放在Unity项目的Assets/StreamingAssets文件夹下面,没有这个文件夹的话自己建一个。
2.读取的代码(假设名为"文件.txt")
if (Application.platform == RuntimePlatform.Android)
string fpath= "jar:file://" + Application.dataPath + "!/assets/" + "文件.txt";
WWW www = new WWW(fpath);
string filepath = Application.dataPath +"/StreamingAssets/"+"my.xml";
string filepath = Application.dataPath +"/Raw/"+"/my.xml";
string filepath = "jar:file://" + Application.dataPath + "!/assets/"+"my.xml;
可以使用Resources.Load(“名字”); 把文件夹中的对象加载出来
可以使用Application.dataPath进行读操作
Application.dataPath: 只可读不可写,放置一些资源数据
iOS与android平台都可以使用这个目录下进行读写操作,可以存放各种配置文件进行修改之类的。
在PC上的地址是:C:\Users\用户名 \AppData\LocalLow\DefaultCompany\test
一. 在项目根目录中创建Resources文件夹来保存文件。
可以使用Resources.Load(“文件名字,注:不包括文件后缀名”);把文件夹中的对象加载出来。
注:此方可实现对文件实施“增删查改”等操作,但打包后不可以更改了。
二. 直接放在项目根路径下来保存文件
在直接使用Application.dataPath来读取文件进行操作。
注:移动端是没有访问权限的。
三. 在项目根目录中创建StreamingAssets文件夹来保存文件。
1.可使用Application.dataPath来读取文件进行操作。
string filepath = Application.dataPath +"/StreamingAssets/"+"my.xml";
string filepath = Application.dataPath +"/Raw/"+"my.xml";
string filepath = "jar:file://" + Application.dataPath + "!/assets/"+"my.xml;
// 上面三个平台各自判断,其实不用那么麻烦,直接统一使用Application.streamingAssets即可// string filepath = Application.streamingAssets + "/my.xml";
2.直接使用Application.streamingAssetsPath来读取文件进行操作。
注:此方法在pc/Mac电脑中可实现对文件实施“增删查改”等操作,但在移动端只支持读取操作。
四. 使用Application.persistentDataPath来操作文件**(荐)**
该文件存在手机沙盒中,因此不能直接存放文件,