多个 Unity 进程使用一个 Unity 工程

在 Unity 构建时想要并行化 BuildAssetBundles 和 BuildPlayer 这两个流程,想要使用多个 Unity 打开一份工程来执行不同的函数,使用复制或者软链接的方式来快速拷贝一份工程出来使用,如下(参考 Unity-MultiProcess-BuildPipeline)使用函数 UnityFork​:

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
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using UnityEditor;

namespace MultiProcessBuild
{
static class MultiProcess
{
static int[] Start(Process[] pss, string title, string info)
{
int total = pss.Length;
int[] exitCodes = new int[total];

try
{
for (int i = 0; i < total; ++i)
{
var ps = pss[i];
if (!ps.Start())
throw new System.Exception("Process Start Failed.");
}

while (true)
{
int progress = pss.Count(x => x.HasExited);
if (EditorUtility.DisplayCancelableProgressBar(title, info, (float)progress / total))
throw new System.Exception("User Cancel.");
Thread.Sleep(200);
if (progress >= total)
break;
}
for (int i = 0; i < total; ++i)
exitCodes[i] = pss[i].ExitCode;
}
finally
{
for (int i = 0; i < total; ++i)
{
var ps = pss[i];
if (!ps.HasExited)
ps.Kill();
ps.Dispose();
}
EditorUtility.ClearProgressBar();
}

return exitCodes;
}

static int[] Start(string bin, string[] cmd, string title, string info)
{
Process[] pss = new Process[cmd.Length];
for (int i = 0; i < cmd.Length; ++i)
{
var ps = new Process();
ps.StartInfo.FileName = bin;
ps.StartInfo.Arguments = cmd[i];
pss[i] = ps;
}
return Start(pss, title, info);
}

static Process mklink(string source, string dest)
{
source = Path.GetFullPath(source);
dest = Path.GetFullPath(dest);
var ps = new Process();
#if UNITY_EDITOR_WIN
ps.StartInfo.FileName = "cmd";
ps.StartInfo.Arguments = string.Format("/c mklink /j \"{0}\" \"{1}\"", dest, source);
#elif UNITY_EDITOR_OSX
ps.StartInfo.FileName = "ln";
ps.StartInfo.Arguments = string.Format("-sf \"{0}\" \"{1}\"", source, dest);
#endif
return ps;
}

static Process rsync(string source, string dest, params string[] ignores)
{
var ps = new Process();
#if UNITY_EDITOR_WIN
string ignore = "";
if (ignores.Length > 0)
{
ignore = "/xd ";
foreach (var x in ignores)
ignore += x + " ";
}
ps.StartInfo.FileName = "robocopy";
ps.StartInfo.Arguments = string.Format("/s {0} {1} {2}", source, dest, ignore);
#elif UNITY_EDITOR_OSX
string ignore = "";
if (ignores.Length > 0)
{
foreach (var x in ignores)
ignore += " --exclude=" + x;
}
ps.StartInfo.FileName = "rsync";
ps.StartInfo.Arguments = string.Format("-r {0} {1} {2}", source, Path.GetDirectoryName(dest), ignore);
#endif
return ps;
}

public static int[] UnityFork(string[] cmds, string title, string info)
{
const string slaveRoot = "../Slaves";
if (!Directory.Exists(slaveRoot))
Directory.CreateDirectory(slaveRoot);

List<Process> linkPSs = new List<Process>();
List<Process> rsyncPSs = new List<Process>();

for (int i = 0; i < cmds.Length; ++i)
{
string slaveProject = string.Format("{0}/slave_{1}", slaveRoot, i);
cmds[i] += " -projectPath " + Path.GetFullPath(slaveProject);
if (!Directory.Exists(slaveProject))
Directory.CreateDirectory(slaveProject);

if (!Directory.Exists(slaveProject + "/Assets"))
{
#if UNITY_EDITOR_WIN
linkPSs.Add(mklink("Assets", slaveProject + "/Assets"));
#elif UNITY_EDITOR_OSX
Directory.CreateDirectory(slaveProject + "/Assets");
#endif
}

#if UNITY_EDITOR_OSX
foreach (var file in Directory.GetFiles("Assets", "*.*", SearchOption.TopDirectoryOnly))
linkPSs.Add(mklink(file, slaveProject + "/Assets"));
foreach (var dir in Directory.GetDirectories("Assets", "*.*", SearchOption.TopDirectoryOnly))
linkPSs.Add(mklink(dir, slaveProject + "/Assets"));
#endif

if (!Directory.Exists(slaveProject + "/ProjectSettings"))
linkPSs.Add(mklink("ProjectSettings", slaveProject + "/ProjectSettings"));
if (!Directory.Exists(slaveProject + "/Library"))
{
Directory.CreateDirectory(slaveProject + "/Library");
linkPSs.Add(mklink("Library/metadata", slaveProject + "/Library/metadata"));
linkPSs.Add(mklink("Library/ShaderCache", slaveProject + "/Library/ShaderCache"));
linkPSs.Add(mklink("Library/AtlasCache", slaveProject + "/Library/AtlasCache"));
}
rsyncPSs.Add(rsync("Library", slaveProject + "/Library", "metadata", "ShaderCache", "AtlasCache", "DependCache"));
}

Start(linkPSs.ToArray(), "make slave projects", "mklinking");
Start(rsyncPSs.ToArray(), "make slave projects", "rsyncing");

string Unity = EditorApplication.applicationPath;
#if UNITY_EDITOR_OSX
Unity += "/Contents/MacOS/Unity";
#endif
return Start(Unity, cmds, title, info);
}
}
}

要注意工程多开后,尽量不要使工程的资源发生变动,从而导致两个 Unity 实例同时导入资源