Alt Dev Blog

Develop Blog


  • 首页

  • 分类

  • 归档

  • 标签

虚幻4 Task Graph System 介绍

发表于 2016-11-25   |   分类于 UE4   |  

taskgraphsystemuml

引言

Task Graph System是虚幻4核心模块中的一层抽象任务处理系统,
通过该基础设施,我们可以创建任意多线程任务, 异步任务, 序列任务, 并行任务等,并可以指定任务顺序, 设置任务间的依赖, 最终形成一个任务图, 该系统按照设定好的依赖关系来分配任务图中的任务到各个线程中执行, 最终执行完整个任务图。

Thread

引擎会根据平台多线程支持情况创建一组线程。在支持多线程的平台上,通常会各有一个NamedThread,多个AnyThread。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace ENamedThreads
{
enum Type
{
UnusedAnchor = -1,
/** The always-present, named threads are listed next **/
#if STATS
StatsThread,
#endif
RHIThread, // 创建一个
AudioThread, // 创建一个
GameThread, // 创建一个
// 不支持多线程的平台(如HTML5) 渲染线程为GameThread, 支持多线程的平台 渲染线程为ActualRenderingThread
ActualRenderingThread = GameThread + 1, // 创建一个
// CAUTION ThreadedRenderingThread must be the last named thread, insert new named threads before it

/** not actually a thread index. Means "Unknown Thread" or "Any Unnamed Thread" **/
AnyThread = 0xff

Task

模板类TGraphTask<TTask> 中类型参数TTask应提供下面几个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TTask {

IStatId GetStatId() const;

// 在哪个线程执行该任务? NamedThread 或 AnyThread
[static] ENamedThreads::Type GetDesiredThread();
/*
namespace ESubsequentsMode
{
enum Type
{
// 存在后续任务
TrackSubsequents,
// 没有后续任务依赖该任务
FireAndForget
};
}
*/

static ESubsequentsMode::Type GetSubsequentsMode();
//在该函数中执行具体任务内容
void DoTask(ENamedThreads::Type, const FGraphEventRef&);
}

FTaskGraphInterface为Task Graph System接口, 负责调度执行任务
FTaskGraphInterface对用户透明,通常使用TGraphTask::CreateTask()创建任务

首先要定义任务类

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
class FHeavyTask 
{
int32 LoopCount;
public:
FHeavyTask(int count): LoopCount{count}
{}
~FHeavyTask(){}
IStatId GetStatId() const
{

RETURN_QUICK_DECLARE_CYCLE_STAT(FHeavyTask, STATGROUP_TaskGraphTasks);
}

static ENamedThreads::Type GetDesiredThread()
{

return ENamedThreads::AnyThread;
}

static ESubsequentsMode::Type GetSubsequentsMode()
{

return ESubsequentsMode::FireAndForget;
}

void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{

// Do real task
}
};
```
创建任务并分发
```cpp
TGraphTask<FHeavyTask>::CreateTask().ConstructAndDispatchWhenReady(100);

ParallelFor

基于TaskGraphSystem的ParallelFor函数,我们可以把某些for循环并行化

1
2
3
4
// 并行执行多个Function
void ParallelFor(int32 Num, TFunctionRef<void(int32)> Body, bool bForceSingleThread = false);

void ParallelForWithPreWork(int32 Num, TFunctionRef<void(int32)> Body TFunctionRef<void()> CurrentThreadWorkToDoBeforeHelping, bool bForceSingleThread = false);

举个栗子:

1
2
3
4
5
6
7
8
9
10
TArray<FString> IntStringArray;
Fill(IntStringArray); // fill IntStringArray
TArray<int32> IntArray;
IntArray.SetNum(IntStringArray.Num());
auto mapper = [&](int32 idx) {
IntArray[idx] = FCString::Atoi(*IntStringArray[idx]);
};

// 并行化
ParallelFor(IntStringArray.Num(), mapper);

Async

1
2
//异步执行一个Function 
void AsyncTask(ENamedThreads::Type Thread, TFunction<void()> Function);

Conclusion

End

Unity协程的内部实现分析并模拟实现

发表于 2016-03-28   |   分类于 Unity   |  

引言

使用Unity协程我们可以非常方便的实现某些延迟逻辑,协程使用IEnumerator + yield语句来实现函数的分段执行。协程不是线程,协程和主逻辑在同一个线程中执行,它使用yield语句将代码段分散在不同帧执行,是一种异步执行代码的方式,但是代码看起来感觉又是同步执行的。

yield return与IEnumerator

C#中使用yield return语句来实现自定义的集合类型的IEnumerable和IEnumerator模式, 而不需要自己实现IEnumerator接口。
当代码执行到yield return expresson,表达式的值返回并可通过IEnumerator.Current访问,同时当前代码运行位置及状态保存了下来,下次执行会从当前位置状态开始执行。IEnumerator.MoveNext()执行下一段代码。通过保存IEnumerator实例就可以控制什么时候执行下一段代码。

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
IEnumerator Numbers()
{

for (int i = 0; i < 3; i++)
{
yield return i;
}

yield return new WaitForSeconds(5);
}

void Start() {
IEnumerator enumerator = Numbers();
Debug.Log(enumerator.Current); // Null

// 遍历下一个元素
enumerator.MoveNext();
Debug.Log(enumerator.Current); // 0
Debug.Log(enumerator.Current); // 0

enumerator.MoveNext();
enumerator.MoveNext();
Debug.Log(enumerator.Current); // 2

enumerator.MoveNext();
// 这里不会真正的WaitForSeconds
Debug.Log(enumerator.Current); // UnityEngine.WaitForSeconds
}

模拟协程

上面代码可以看到,yield return语句把函数分段,而什么时候执行这些函数断是通过调用IEnumerator.MoveNext()来决定的。我们保存IEnumerator实例, 将MoveNext()放到MonoBehaviour.Update()函数中执行,则这些片段就分散在每帧中执行了。

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
IEnumerator enumerator;

void StartRoutine(IEnumerator coroutine)
{

enumerator = Routine();
}

IEnumerator Routine()
{

Debug.Log("step1.");
yield return 1;
Debug.Log("step2.");
yield return 2;
Debug.Log("step3.");
// 这里实际没有等待1秒, 现在表达式的值没有用到
yield return new WaitForSeconds(1);
Debug.Log("step4.");
}

void Start()
{

StartRoutine(Routine());
}
void Update()
{

if (enumerator != null)
{
if (!enumerator.MoveNext())
{
enumerator = null;
}
}
}

Routine代码总共分了4帧执行完毕,到目前为止我们还没有把yield return表达式的值没有运用起来,比如WaitForSeconds没有真正等待。我们可以通过Current访问yield return表达式的值来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void Update()
{

if (enumerator != null)
{
var waiting = enumerator.Current as WaitForSeconds;
if (waiting != null && !waiting.isDone) // 伪代码
{
// 保持等待,不调用MoveNext()
}
else if (!enumerator.MoveNext())
{

enumerator = null;
}
}
}

完整模拟代码

上面的代码简单的描述了协程实现原理,实际的协程实现远比这复杂的多,需要实现嵌套协程,异步操作等等。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/// <summary>
/// 协程实现伪代码
/// </summary>
public class CoroutinePseudoRuntime
{
private List<IEnumerator> coroutines = new List<IEnumerator>();

private List<IEnumerator> needRemoveCoroutines = new List<IEnumerator>();

public void RunNormalCoroutine()
{

for (int i = 0; i < coroutines.Count; i++)
{
if (!ExecuteNormalCoroutine(coroutines[i]))
{
// 协程已经执行完毕,需要删除
needRemoveCoroutines.Add(coroutines[i]);
}
}

GCCoroutines();
}

public void RunParticularCoroutine<T>() where T : YieldInstruction
{
for (int i = 0; i < coroutines.Count; i++)
{
if (!ExecuteParticularCoroutine<T>(coroutines[i]))
{
needRemoveCoroutines.Add(coroutines[i]);
}
}

GCCoroutines();
}

/// <summary>
/// 递归执行特定yield instruction
/// </summary>
/// <typeparam name="T">特定yield instruction</typeparam>
/// <param name="coroutine">协程</param>
/// <returns>协程是否在执行中</returns>
bool ExecuteParticularCoroutine<T>(IEnumerator coroutine) where T : YieldInstruction
{
IEnumerator child = coroutine.Current as IEnumerator;
if (child != null)
{
// 在子协程中执行特定yield instruction
bool childResult = ExecuteParticularCoroutine<T>(child);
if (childResult == false)
{
// 子协程执行完毕 继续执行父协程
return coroutine.MoveNext();
}

return childResult;
}

T current = coroutine.Current as T;
if (current != null)
{
return coroutine.MoveNext();
}

// 没有找到特定yield instruction
return true;
}

bool ExecuteNormalCoroutine(IEnumerator coroutine)
{

var waitForEndOfFrame = coroutine.Current as WaitForEndOfFrame;
if (waitForEndOfFrame != null)
{
// 等待WaitForEndOfFrame执行完毕
// 在LateUpdate后执行
return true;
}

var waitForFixedUpdate = coroutine.Current as WaitForFixedUpdate;
if (waitForFixedUpdate != null)
{
// 等待WaitForFixedUpdate执行完毕
// 在FixedUpdate后执行
return true;
}

var waitForSeconds = coroutine.Current as WaitForSeconds;
if (waitForSeconds != null)
{
// 等待WaitForSeconds isDone是伪代码
//if (waitForSeconds.isDone)
//{
// return true;
//}
return coroutine.MoveNext();
}

var www = coroutine.Current as WWW;
if (www != null)
{
// 等待www完成
if (www.isDone)
{
return coroutine.MoveNext();
}
return true;
}

var asyncOperation = coroutine.Current as AsyncOperation;
if (asyncOperation != null)
{
// 等待异步操作完成
if (asyncOperation.isDone)
{
return coroutine.MoveNext();
}
return true;
}

var childCoroutine = coroutine.Current as IEnumerator;
if (childCoroutine != null)
{
// 执行子协程
bool waiting = ExecuteNormalCoroutine(childCoroutine);
if (waiting)
{
return true;
}
return coroutine.MoveNext();
}

// 这里省略了其他的YieldInstruction


// null 及 其他
return coroutine.MoveNext();
}

void GCCoroutines()
{

for (int i = 0; i < needRemoveCoroutines.Count; i++)
{
coroutines.Remove(needRemoveCoroutines[i]);
}
}

/// <summary>
/// 开启一个协程
/// </summary>
/// <param name="coroutine"></param>
public void StartCoroutine(IEnumerator coroutine)
{

coroutines.Add(coroutine);
}

/// <summary>
/// 停止一个协程
/// </summary>
/// <param name="coroutine"></param>
/// <returns></returns>
public bool StopCoroutine(IEnumerator coroutine)
{

return coroutines.Remove(coroutine);
}
}

public class CoroutinePseudoBehaviour : MonoBehaviour
{
void Start()
{

coroutineRuntime.StartCoroutine(CoroutineTest());
}

IEnumerator WaitUntilUnscaled(float until)
{

while (Time.unscaledTime < until)
{
yield return null;
}
}

IEnumerator WaitForFrame(int frameCount)
{

for (int i = 0; i < frameCount; i++)
{
yield return new WaitForEndOfFrame();
}
}

IEnumerator WaitUntilThenFrame(float until, int frame)
{

yield return WaitUntilUnscaled(until);
Debug.Log("after wait until...");
yield return WaitForFrame(frame);
Debug.LogFormat("after wait {0} frames...", frame);
}

IEnumerator CoroutineTest()
{

Debug.Log("start...");
yield return new WaitForFixedUpdate();
Debug.Log("after Fixed update...");
yield return new WaitForEndOfFrame();
Debug.Log("after late update...");
yield return (WaitUntilUnscaled(Time.unscaledTime + 1));
Debug.Log("1 seconds elapsed...");

yield return WaitUntilThenFrame(Time.unscaledTime + 1, 10);
Debug.Log("after 1 seconds and 10 frames...");

WWW www = new WWW("http://docs.unity3d.com/ScriptReference/CustomYieldInstruction.html");
yield return www;
Debug.Log(www.text);
}

CoroutinePseudoRuntime coroutineRuntime = new CoroutinePseudoRuntime();

void Update()
{

// 一般的协程在Update之后 LateUpdate之前执行
coroutineRuntime.RunNormalCoroutine();
}

void LateUpdate()
{

// Unity中WaitForEndOfFrame在 LateUpdate之后执行 这里模拟在MonoBehavior.LateUpdate中
coroutineRuntime.RunParticularCoroutine<WaitForEndOfFrame>();
}

void FixedUpdate()
{

// Unity中 WaitForFixedUpdate在FixedUpdate之后执行 这里模拟在MonoBehavior.FixedUpdate中
coroutineRuntime.RunParticularCoroutine<WaitForFixedUpdate>();
}
}

结语

上面简单分析了yield return语句和IEnumerator接口这一实现协程的C#基础概念,并利用他们模拟实现了一个协程运行时库,可以清楚的看到协程不外乎就是把代码分散到不同时间执行,它跟线程是有本质的区别的。

Unity 5.3版本中协程自定义yield语句

发表于 2016-03-28   |   分类于 Unity   |  

本文介绍几种Unity协程中自定义yield语句的方法,并对比其在5.3版本和旧版本的中实现的区别。

引言

Unity中Coroutine非常好用,但是5.3之前的版本不能优雅的实现自定义yield语句。Unity5.3中引入了CustomYieldInstruction类并改变了一些内部yield 语句规则,让我们优雅的实现自定义yield语句。

继承CustomYieldInstruction

5.3引入了新的类CustomYieldInstruction使开发者可以自定义yield语句。通过继承CustomYieldInstruction override keepWaiting 的值决定是否暂停该协程。

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
public class CustomWaitForSeconds : CustomYieldInstruction
{
private float until;
// 上层协程(枚举器)是否继续等待
public override bool keepWaiting
{
get
{
return Time.unscaledTime < until;
}
}

public CustomWaitForSeconds(float seconds)
{

until = Time.unscaledTime + seconds;
}
}

public class WaitForSecondsTest : MonoBehaviour
{
IEnumerator Start()
{

yield return new CustomWaitForSeconds(5);
Debug.Log("5 seconds elapsed...");
}
}

IEnumerator接口

我们也可以通过实现IEnumerator接口来实现自定义yield语句。接口MoveNext()返回布尔值觉得决定上层协程(枚举器)是否继续等待。但是之前的版本中代码中要这样yield return StartCoroutine(enumerator);, 手动启动一个子协程,等待这个子协程执行完毕。5.3版本简化了这种写法, 协程(枚举器)IEnumerator的Current对象是实现了IEnumerator接口的话,则会等待这个子协程对象遍历完毕后才继续执行(文档)。 也就是父协程(枚举器)会等待Current子协程(枚举器)执行完毕(Current对象实现了IEnumerator)才会继续执行。 所以新版本中只需要yield return enumerator;就可以了, Unity内部会自动开启子协程。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
public class WaitForSecondsUnscaled : IEnumerator
{
private float until;

public object Current { get { return null; } }

public WaitForSecondsUnscaled(float seconds)
{

seconds = Mathf.Max(seconds, 0);
until = Time.unscaledTime + seconds;
}
// 返回为true则父协程暂停,等待该协程继续执行;返回为false父协程继续执行
public bool MoveNext()
{

return Time.unscaledTime < until;
}

public void Reset()
{

until = 0;
}
}

public class WaitForSecondsTest : MonoBehaviour
{
IEnumerator Start()
{

yield return new CustomWaitForSeconds(5);
Debug.Log("5 seconds elapsed...");
// 新方式
yield return new WaitForSecondsUnscaled(5);
// 旧方式
//yield return StartCoroutine(new WaitForSecondsUnscaled(5));
Debug.Log("10 seconds elapsed...");
// 新方式
yield return WaitUntilUnscaled(Time.unscaledTime + 5);
// 旧方式
//yield return StartCoroutine(WaitUntilUnscaled(Time.unscaledTime + 5));
Debug.Log("15 seconds elapsed...");
}

// 返回IEnumerator接口实例的函数
IEnumerator WaitUntilUnscaled(float until)
{

while (Time.unscaledTime < until)
{
yield return null;
}
}
}

结语

上面两个例子可以看出5.3版本中的自定义yield语句非常好用,对比旧版本更清晰更直观。请记住协程好用但是不能滥用,用好了协程会使逻辑更清晰,滥用容易造成GC压力。

Unity5.3新版场景管理

发表于 2016-03-25   |   分类于 Unity   |  

本文介绍如何使用Unity5.3引入的新API UnityEngine.SceneManagement来进行场景和关卡的管理。

介绍

经过5个版本的发展, Unity5.3放出了统一合理的场景管理API。 在之前的版本中场景即代码中关卡(Level)。 但是使用Unity过程中很快便发现用场景来定义不同的关卡不是必须的,所以就引入了 Scene API.
之前版本中的关卡在逻辑角度上指明了此关卡的构建索引号(即Build Settins中此关卡的序号), 场景则指明他们的包含该场景的资源名。 然而这两个概念都不是识别一个真正场景的可靠唯一ID。Build Settings中移动场景会改变他们的索引。同样的,不同文件夹下可能会有两个相同名字的场景。Unity5.3尝试结束这些混乱的逻辑。

5.3版本中新的场景API

Unity5.3之前使用构建索引和场景资源名都能指向一个场景。Unity5.3引入了一个新的Scene结构体来指明一个场景。它封装了一些场景常用方法和变量如buildIndex, name和path。

访问SceneManager类

要想使用新的场景API,C#代码需要引用SceneManagement命名空间

1
using UnityEngine.SceneManagement;

这样就可以使用SceneManager类来替换Application中废弃的相关场景函数。

获取当前场景

Unity改用“场景”而不是“关卡”的一个主要原因是你可以同时加载多个场景。这样就破坏了“当前关卡”这个概念,它现在被替换成了“激活场景”。多数情况下,激活场景为最新加载的场景。我们可以任意时刻使用GetActiveScene获取激活场景。

1
2
3
4
5
6
7
8
// 5.3
Scene scene = SceneManager.GetActiveScene();
Debug.Log(scene.name);
Debug.Log(scene.buildIndex);

// 5.2
Debug.Log(Application.loadedLevelName);
Debug.Log(Application.loadedLevel);

检测激活场景是不是某个指定场景名:

1
2
3
4
if (SceneManager.GetActiveScene().name == "sceneName")
{
// ...
}

也可以使用重载的==操作符来检测:

1
2
3
4
if (SceneManager.GetActiveScene() == scene)
{
// ...
}

加载场景

和之前的版本一样,我们可以通过构建索引和场景名来加载场景,两个场景名一样但路径不同的话推荐使用路径来加载指定场景,否则Unity会加载第一个匹配名字的场景。

加载单个场景

默认是加载单个场景,加载新场景会卸载所有已加载的旧场景,销毁所有旧场景的GameObjects。

1
2
3
4
5
6
7
8
// 5.3
SceneManager.LoadScene(4);
SceneManager.LoadScene("sceneName"); //场景名方式,忽略大小写
SceneManager.LoadScene("path/to/scene/file/sceneName");//场景资源路径方式,忽略大小写

// 5.2
Application.LoadLevel(4);
Application.LoadLevel("sceneName");

异步版本:

1
2
3
4
5
6
7
8
// 5.3
SceneManager.LoadSceneAsync(4);
SceneManager.LoadSceneAsync("sceneName");
SceneManager.LoadSceneAsync("path/to/scene/file/sceneName");

// 5.2
Application.LoadLevelAsync(4);
Application.LoadLevelAsync("sceneName");

添加方式加载场景

我们也可以通过指定SceneManagement.LoadSceneMode.Additive以添加新场景的方式来加载新场景:

1
2
3
4
5
6
7
8
// 5.3
SceneManager.LoadScene(4, SceneManagement.LoadSceneMode.Additive);
SceneManager.LoadScene("sceneName", SceneManagement.LoadSceneMode.Additive);
SceneManager.LoadScene("path/to/scene/file/sceneName", SceneManagement.LoadSceneMode.Additive);

// 5.2
Application.LoadLevelAdditive(4);
Application.LoadLevelAdditive("sceneName");

异步版本:

1
2
3
4
5
6
7
8
// 5.3
SceneManager.LoadSceneAsync(4, SceneManagement.LoadSceneMode.Additive);
SceneManager.LoadSceneAsync("sceneName", SceneManagement.LoadSceneMode.Additive);
SceneManager.LoadSceneAsync("path/to/scene/file/sceneName", SceneManagement.LoadSceneMode.Additive);

// 5.2
Application.LoadLevelAdditiveAsync(4);
Application.LoadLevelAdditiveAsync("sceneName");

卸载场景

Unity5.3同时也提供UnloadScene方法来卸载指定场景:

1
2
3
4
// 5.3
SceneManager.UnloadScene(4); // 通过构建索引卸载
SceneManager.UnloadScene("sceneName"); // 通过场景名或场景路径卸载
SceneManager.UnloadScene(sceneStruct); // 通过Scene结构体卸载(加载场景却没有提供通过Scene结构体加载的方式)

该函数返回是否成功卸载场景。

总结

上面简单介绍了5.3版本中的场景管理API, 我们看到Unity中新的场景管理方式提供了一种更清晰可靠的动态加载管理的流程。如果你在用5.3版本还是尽快更新代码使用新API来管理场景吧。

参考 Scene Management in Unity 5

iOS ViewController入门

发表于 2016-03-15   |   分类于 iOS   |  

ViewController是App最基础的结构, 一个App由1到多个ViewController组成,
ViewController扮演MVC模式中的C(Controller), 展示并管理子视图树结构.
通过子类化UIViewController来实现实现自定义数据模型和视图之间的逻辑和通信

ViewController 分类

  • Content ViewController 直接管理内容子View显示
  • Container ViewController 以不同的方式管理和显示子ViewController

ViewController 展示和过渡

presenting view controller vs presented view controller

ViewController 显示

  • segue
  • showViewController:sender:
  • presentViewControllerAnimated:completion:

ViewController 关闭

在presenting view controller中调用dismissViewControllerAnimated:completion:关闭一个presented view controller.

presented view controller返回数据用delegation模式, 参考UIImagePickerController和UIImagePickerControllerDelegate

Unity: 自定义角色中的蒙皮网格替换

发表于 2015-03-19   |   分类于 Unity   |  

Unity允许运行时替换SkinnedMeshRenderer的网格来实现自定义角色部件,分两种情况Optimize Game Objects 于 Nonoptimized GameObjects

Optimize Game Objects是模型Rig选项,开启后骨架不会在场景中存在,而是放入了内部存储。这种情况下只需简单替换mesh就行,不需要重新设置骨骼信息

1
2
3
4
5
6
public static void ReplaceSkinnedMeshOptimized(SkinnedMeshRenderer src, SkinnedMeshRenderer dst)
{

dst.sharedMaterials = src.sharedMaterials;
dst.sharedMesh = src.sharedMesh;
dst.localBounds = src.localBounds;
}

对于没有优化的Rig 需要重新设置骨骼信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void ReplaceSkinnedMeshNonOptimized(SkinnedMeshRenderer src, SkinnedMeshRenderer dst)
{

Dictionary<string, Transform> skeleton = new Dictionary<string, Transform>();
Transform[] allTrans = dst.transform.GetComponentsInChildren<Transform>(true);
foreach (var item in allTrans)
{
skeleton[item.name] = item;
}

dst.sharedMaterials = src.sharedMaterials;
dst.sharedMesh = src.sharedMesh;
Transform[] bones = new Transform[src.bones.Length];
for (int ii = 0; ii < bones.Length; ii++)
{
bones[ii] = skeleton[src.bones[ii].name];
}

dst.bones = bones;
dst.rootBone = skeleton[src.rootBone.name];
dst.localBounds = src.localBounds;
}

Unity从AssetBundle加载场景

发表于 2015-03-18   |   分类于 Unity   |  

由于Unity5有一套新的AssetBundle机制,所以这里讲的仅适用于Unity4.x

打包场景

1
2
3
4
5
6
7
8
9
10
11
[MenuItem("Tools/BuildStreamingScene")]
public static void BuildStreamingScene()
{

var path = SelectSaveFolderPath();
if (string.IsNullOrEmpty(path))
{
return;
}
string[] levels = new string[] { "Assets/Game/Scenes/Level1.unity" };
BuildPipeline.BuildStreamedSceneAssetBundle(levels, path + "Scenes.ab", BuildTarget.Android, BuildOptions.BuildAdditionalStreamedScenes);
}

加载场景

由于Application.LoadLevel实际上会延迟加载场景对象,如果提前调用ab.Unload(false)会造成场景对象被清空,所以ab.Unload(false)应在场景加载完成后调用 在OnLevelWasLoaded事件中Unload.

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
AssetBundle ab { set; get; }
IEnumerator Start()
{

yield return new WaitForSeconds(1.0f);

WWW www = new WWW(new System.Uri(Application.streamingAssetsPath + "/scenes/Scenes.ab").AbsoluteUri);
yield return www;
ab = www.assetBundle;

if (Application.CanStreamedLevelBeLoaded("Level1"))
{
Application.LoadLevel("Level1");
}
//ab.Unload(false); // 此处会施放AssetBundle会造成空场景
yield return null;
}

void OnLevelWasLoaded(int levelId)
{

if (ab != null)
{
ab.Unload(false);
}
Debug.Log("On Level Was Loaded .. " + Application.loadedLevelName);
}

完

BenLoong

BenLoong

Programming my world.

7 日志
3 分类
5 标签
GitHub Weibo Twitter
© 2015 - 2016 BenLoong
由 Hexo 强力驱动
主题 - NexT.Muse