2014.07.24转载地址:http://blog.gamerisker.com/archives/461.html
这几天一直在研究热更新方案
主要思路是:
1.先将代码打包成dll,然后用unity 打包成assetsbundle,
2.WWW加载进入主程序,
3使用System.Reflection.Assembly来创建程序集,
4.然后通过GetType(className),来获取这个类
5.AddComponent进入主程序,加载的dll就执行起来了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//打包工具,该工具是网上找来都。谢谢作者!
public
class
ExportAssetBundles
:
MonoBehaviour
{
//在Unity编辑器中添加菜单
[
MenuItem
(
"Custom Editor/Create AssetBunldes ALL"
)
]
static
void
ExportResource
(
)
{
// 打开保存面板,获得用户选择的路径
string
path
=
EditorUtility
.
SaveFilePanel
(
"Save Resource"
,
""
,
"New Resource"
,
"assetbundle"
)
;
if
(
path
.
Length
!=
0
)
{
// 选择的要保存的对象
Object
[
]
selection
=
Selection
.
GetFiltered
(
typeof
(
Object
)
,
SelectionMode
.
DeepAssets
)
;
//打包
BuildPipeline
.
BuildAssetBundle
(
Selection
.
activeObject
,
selection
,
path
,
BuildAssetBundleOptions
.
CollectDependencies
|
BuildAssetBundleOptions
.
CompleteAssets
,
BuildTarget
.
StandaloneWindows
)
;
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
using
UnityEngine
;
using
System
.
Collections
;
using
System
.
Reflection
;
//代码加载器
public
class
Index
:
MonoBehaviour
{
private
WWW
www
;
public
static
WWW
uiWWW
;
private
System
.
Reflection
.
Assembly
assembly
;
// Use this for initialization
void
Start
(
)
{
StartCoroutine
(
loadScript
(
)
)
;
}
private
IEnumerator
loadScript
(
)
{
//加载我的代码资源
www
=
new
WWW
(
"http://localhost/Main.assetbundle"
)
;
yield
return
www
;
AssetBundle
bundle
=
www
.
assetBundle
;
TextAsset
asset
=
bundle
.
Load
(
"Main"
,
typeof
(
TextAsset
)
)
as
TextAsset
;
assembly
=
System
.
Reflection
.
Assembly
.
Load
(
asset
.
bytes
)
;
Assembly
[
]
assLis
=
System
.
AppDomain
.
CurrentDomain
.
GetAssemblies
(
)
;
System
.
Type
script
=
assembly
.
GetType
(
"Main"
)
;
gameObject
.
AddComponent
(
script
)
;
}
}
|
因为在加载的时候遇见安全沙箱问题,所以我将这个策略文件记录下来,方便下次复制粘贴
1
2
3
4
5
6
|
xml
version
=
"1.0"
?>
|
本地调试程序时解决跨域问题的方法:
Edit->Project Settings->Eidtor
刚开始的时候想使用序列化来存储一些数据,但是后来却连一个很简单的类序列化dll里面都没法获得
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
[
MenuItem
(
"Custom Editor/WriteSpriteData"
)
]
static
void
FileWriteSpriteData
(
)
{
TextAsset
textasset
=
AssetDatabase
.
LoadAssetAtPath
(
"Assets/Resources/Packer/Packer.txt"
,
typeof
(
TextAsset
)
)
as
TextAsset
;
Atlas
atlas
=
ScriptableObject
.
CreateInstance
<
Atlas
>
(
)
;
//Json其实是NGUIJson这个类,我只是把他提出来。改了个名字
atlas
.
mList
=
Json
.
LoadSpriteData
(
textasset
as
TextAsset
)
;
if
(
atlas
.
mList
==
null
)
return
;
string
path
=
"Assets/Resources/Packer/Packer.asset"
;
AssetDatabase
.
CreateAsset
(
atlas
,
path
)
;
//Atlas是一个只有一个mList属性都类 mList = new List
Object
o
=
AssetDatabase
.
LoadAssetAtPath
(
path
,
typeof
(
Atlas
)
)
;
Object
texture
=
AssetDatabase
.
LoadAssetAtPath
(
"Assets/Resources/Packer/Packer.mat"
,
typeof
(
Material
)
)
;
Object
[
]
t
=
{
texture
}
;
BuildPipeline
.
BuildAssetBundle
(
o
,
t
,
"Assets/Resources/Packer/Packer.assetbundle"
)
;
//AssetDatabase.DeleteAsset(path);
}
|
这是使用序列化数据的加载方式,在不用反射的情况下,下面代码加载能够成功,但是使用了反射,下面的代码就加载不成功了。这个问题我也很费解,暂时我没办法解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
IEnumerator
LoadAtlas
(
)
{
www
=
new
WWW
(
"http://localhost/Packer.assetbundle"
)
;
//WoodenAtlas.assetbundle
//Packer.assetbundle
yield
return
www
;
//用来断点都时候看看里面所包含都数据
Object
[
]
os
=
www
.
assetBundle
.
LoadAll
(
)
;
Material
mete
=
www
.
assetBundle
.
Load
(
"Packer"
,
typeof
(
Material
)
)
as
Material
;
Atlas
atlas
=
www
.
assetBundle
.
mainAsset
as
Atlas
;
GameObject
go
=
new
GameObject
(
"UIAtlas"
)
;
UIAtlas
uiatlas
=
go
.
AddComponent
<
UIAtlas
>
(
)
;
uiatlas
.
spriteMaterial
=
mete
;
uiatlas
.
spriteList
=
atlas
.
mList
;
GameObject
sprite
=
new
GameObject
(
"Sprite"
)
;
UISprite
ui
=
NGUITools
.
AddChild
<
UISprite
>
(
sprite
)
;
ui
.
atlas
=
uiatlas
;
ui
.
spriteName
=
"dynamite"
;
Debug
.
Log
(
"Load"
)
;
www
.
assetBundle
.
Unload
(
false
)
;
www
.
Dispose
(
)
;
}
|
因为要看一下代码的执行效率,所以我寻找到了这个类。感觉还可以。使用josn数据,mat文件创建一个UIAtlas的时间大概是30毫秒左右。
1
2
3
4
5
|
System
.
Diagnostics
.
Stopwatch
;
Stopwatch
stopWatch
=
new
Stopwatch
(
)
;
stopWatch
.
Start
(
)
;
Thread
.
Sleep
(
10000
)
;
stopWatch
.
Stop
(
)
;
|
总结:
我使用没有任何Unity环境以外代码来实现壳的制作(我们暂且将其称为Index,其实他就是上面的Index类,代码少得可怜。)
然后主程序是在另一个Unity项目中(这个项目在发布的时候打包成Main.dll)
Main.dll项目通过上面的Index来加载,然后添加到一个GameObject上,主程序的Awake()方法就会执行(Awake是整个程序的主入口)
这个时候所有的资源加载都会在Main.dll里面完成。
在这个过程中,遇到了一个比较麻烦的问题就是,我打包的一些UIAtlas.prefab文件上的UIAtlas这个类,无法找到。
这让我有一些无法理解,因为NGUI的代码已经打包进入了Main.dll,那么为什么我加载prefab的时候,却找不到UIAtlas这个类呢?
最后我只能动态的制作UIAtlas对象来完成这样工作。
那么这样的话,以后做界面,做任何prefab都不能绑定脚本了。都只能加载到内存中动态AddComponent了。这样界面也得用配置文件了。
不过对于我这种从页游转过来的程序,这到不是问题,我有现成的界面编辑器(我博客里有RookieEditor),直接生成XML在游戏中进行组装了。对于能够热更新来说,这点麻烦,其实应该不算麻烦了。
动态生成UIAtlas后,创建了几个Sprite、Button,基本的功能都已经实现,说明这个解决方案是可行的。接下来我将把这个方案运用到我的项目中,更加全面的去实验一下。
这样设计的优点:
1、对IOS的打包也是比较方便。打包IOS 直接拿Main项目打包就可以了。因为不需要热更新了。把代码打包在本地就行了。
2、打包Android项目的时候发布apk只需要发布Index,项目发布版本和没写代码一样大,想到这里我想吐槽一下,Unity就算不写任何代码,发布一个apk也得有7M左右。这也太大了点吧。我可啥都没做啊。
求助:
1、哪位大神能给我说说上面我遇到的那个问题,为什么找不到绑定在prefab上的类呢?这是程序集的问题么?哎,刚转C#的人伤不起。