编译
上面基本的术语已经介绍完了,下面我们进入来进入蓝图编译过程分析。蓝图的编译过程都在FKismetCompilerContext::Compile()函数中。它根据编译的类型不同(上文编译选项中提到的只编译Skeleton、只生成字节码、只生成cpp代码等等)会走不同的分支。我们这里以完全编译来讲解。此处为大概的流程,若想看详细的流程,请参照流程图以及代码。
清除类
类是就地编译的,这意味着同一个UBlueprintGeneratedClass在每次编译的时候都会被清理,并且会重复利用,这样指向这个类的指针就不用修复了。CleanAndSanitizeClass()把属性和函数放到临时包的一个垃圾(trash)类里面,然后清除类中的所有数据。
创建类的属性
CreateClassVariablesFromBlueprint()函数遍历蓝图的NewVariables数组,也包括构造脚本、Timeline等地方来找到该类需要的所有属性,然后创建UProperties。
创建函数列表
通过处理事件图表(Event graph)、常规的函数图表、代理、以及接口中来创建函数。

处理事件图表
CreateAndProcessUberGraph()函数用来处理事件图表。它把所有的事件图表拷贝到一个大的图中,这个时候节点有机会去做展开操作(expand,如果它需要的话)。然后会为图中的每一个事件节点创建一个函数桩(stub),最终会为当前的事件图表创建一个FKismetFunctionContext,用于把整个事件图表当作一个函数来处理。
处理函数图表
常规函数图表的处理是通过ProcessOneFunctionGraph()函数来完成的,它会把图中的每一个节点拷贝到另外一个节点中去,这个时候每个节点有机会展开(expand),最后会为每一个函数创建一个FKismetFunctionContext,用来后面对该函数的编译工作。
预编译函数
函数的预编译是通过为每一个FKismetFunctionContext调用PrecompileFunction()来实现的,这个函数主要做以下操作:
- 确定执行顺序并计算数据依赖。
- 去除那些没有连接的或者无数据依赖的节点。
- 为每一个剩下的运行每一个节点处理器(FNodeHandlingFunctor)的RegisterNets()函数,它会为函数中的值创建FBPTerminal。
- 创建UFunction对象和相关的属性。

绑定和链接类
现在类已经拥有了UFunctions和UProperties,现在可以绑定和链接类了,它包含填充属性链,属性大小 以及函数的列表。这个时候它相关于有了一个类头文件,不包括最终的标记、元数据以及CDO对象。
编译函数
接下来就需要通过每一个节点处理器(FNodeHanlingFunctor)的Compile()函数使用AppendStatementForNode()函数来为该节点添加FKismetBlueprintStatement。这个函数也可能会创建FBPTerminal对象只要它们只是局部使用的。

后编译函数
PostCompileFunction()是编译函数的最后一个阶段,在所有函数调用了CompileFunction()之后调用,主要是修复交叉引用。

完成编译类
为了完成编译该类,编译器会最终设置类标记,并从父类继承标记和元数据,最后会确定所有的事情在编译过程中是正确的。
后端生成代码
编译器后端会把函数中的所有语句转换成代码,目前有两个使用的后端:
- FKismetCompilerVMBackend 把FKismetCompilerStatement转成字节码,并把它序列化到脚本数组中。

- FKismetCppBackend 生成C++代码,只用于调试目的。
字节码
字节码的定义在Script.h中的EExprToken枚举中,定义如下,可以看到它有一些比较通用的指令,如EX_Jump EX_JumpIfNot等,也有一些专用的指令比如EX_DynamicCast EX_SetArray。
1 //
2
3 // Evaluatable expression item types.
4
5 //
6
7 enum EExprToken
8
9 {
10
11 // Variable references.
12
13 EX_LocalVariable = 0x00, // A local variable.
14
15 EX_InstanceVariable = 0x01, // An object variable.
16
17 EX_DefaultVariable = 0x02, // Default variable for a class context.
18
19 // = 0x03,
20
21 EX_Return = 0x04, // Return from function.
22
23 // = 0x05,
24
25 EX_Jump = 0x06, // Goto a local address in code.
26
27 EX_JumpIfNot = 0x07, // Goto if not expression.
28
29 // = 0x08,
30
31 EX_Assert = 0x09, // Assertion.
32
33 // = 0x0A,
34
35 EX_Nothing = 0x0B, // No operation.
36
37 // = 0x0C,
38
39 // = 0x0D,
40
41 // = 0x0E,
42
43 EX_Let = 0x0F, // Assign an arbitrary size value to a variable.
44
45 // = 0x10,
46
47 // = 0x11,
48
49 EX_ClassContext = 0x12, // Class default object context.
50
51 EX_MetaCast = 0x13, // Metaclass cast.
52
53 EX_LetBool = 0x14, // Let boolean variable.
54
55 EX_EndParmValue = 0x15, // end of default value for optional function parameter
56
57 EX_EndFunctionParms = 0x16, // End of function call parameters.
58
59 EX_Self = 0x17, // Self object.
60
61 EX_Skip = 0x18, // Skippable expression.
62
63 EX_Context = 0x19, // Call a function through an object context.
64
65 EX_Context_FailSilent = 0x1A, // Call a function through an object context (can fail silently if the context is NULL; only generated for functions that don't have output or return values).
66
67 EX_VirtualFunction = 0x1B, // A function call with parameters.
68
69 EX_FinalFunction = 0x1C, // A prebound function call with parameters.
70
71 EX_IntConst = 0x1D, // Int constant.
72
73 EX_FloatConst = 0x1E, // Floating point constant.
74
75 EX_StringConst = 0x1F, // String constant.
76
77 EX_ObjectConst = 0x20, // An object constant.
78
79 EX_NameConst = 0x21, // A name constant.
80
81 EX_RotationConst = 0x22, // A rotation constant.
82
83 EX_VectorConst = 0x23, // A vector constant.
84
85 EX_ByteConst = 0x24, // A byte constant.
86
87 EX_IntZero = 0x25, // Zero.
88
89 EX_IntOne = 0x26, // One.
90
91 EX_True = 0x27, // Bool True.
92
93 EX_False = 0x28, // Bool False.
94
95 EX_TextConst = 0x29, // FText constant
96
97 EX_NoObject = 0x2A, // NoObject.
98
99 EX_TransformConst = 0x2B, // A transform constant
100
101 EX_IntConstByte = 0x2C, // Int constant that requires 1 byte.
102
103 EX_NoInterface = 0x2D, // A null interface (similar to EX_NoObject, but for interfaces)
104
105 EX_DynamicCast = 0x2E, // Safe dynamic class casting.
106
107 EX_StructConst = 0x2F, // An arbitrary UStruct constant
108
109 EX_EndStructConst = 0x30, // End of UStruct constant
110
111 EX_SetArray = 0x31, // Set the value of arbitrary array
112
113 EX_EndArray = 0x32,
114
115 // = 0x33,
116
117 EX_UnicodeStringConst = 0x34, // Unicode string constant.
118
119 EX_Int64Const = 0x35, // 64-bit integer constant.
120
121 EX_UInt64Const = 0x36, // 64-bit unsigned integer constant.
122
123 // = 0x37,
124
125 EX_PrimitiveCast = 0x38, // A casting operator for primitives which reads the type as the subsequent byte
126
127 // = 0x39,
128
129 // = 0x3A,
130
131 // = 0x3B,
132
133 // = 0x3C,
134
135 // = 0x3D,
136
137 // = 0x3E,
138
139 // = 0x3F,
140
141 // = 0x40,
142
143 // = 0x41,
144
145 EX_StructMemberContext = 0x42, // Context expression to address a property within a struct
146
147 EX_LetMulticastDelegate = 0x43, // Assignment to a multi-cast delegate
148
149 EX_LetDelegate = 0x44, // Assignment to a delegate
150
151 // = 0x45,
152
153 // = 0x46, // CST_ObjectToInterface
154
155 // = 0x47, // CST_ObjectToBool
156
157 EX_LocalOutVariable = 0x48, // local out (pass by reference) function parameter
158
159 // = 0x49, // CST_InterfaceToBool
160
161 EX_DeprecatedOp4A = 0x4A,
162
163 EX_InstanceDelegate = 0x4B, // const reference to a delegate or normal function object
164
165 EX_PushExecutionFlow = 0x4C, // push an address on to the execution flow stack for future execution when a EX_PopExecutionFlow is executed. Execution continues on normally and doesn't change to the pushed address.
166
167 EX_PopExecutionFlow = 0x4D, // continue execution at the last address previously pushed onto the execution flow stack.
168
169 EX_ComputedJump = 0x4E, // Goto a local address in code, specified by an integer value.
170
171 EX_PopExecutionFlowIfNot = 0x4F, // continue execution at the last address previously pushed onto the execution flow stack, if the condition is not true.
172
173 EX_Breakpoint = 0x50, // Breakpoint. Only observed in the editor, otherwise it behaves like EX_Nothing.
174
175 EX_InterfaceContext = 0x51, // Call a function through a native interface variable
176
177 EX_ObjToInterfaceCast = 0x52, // Converting an object reference to native interface variable
178
179 EX_EndOfScript = 0x53, // Last byte in script code
180
181 EX_CrossInterfaceCast = 0x54, // Converting an interface variable reference to native interface variable
182
183 EX_InterfaceToObjCast = 0x55, // Converting an interface variable reference to an object
184
185 // = 0x56,
186
187 // = 0x57,
188
189 // = 0x58,
190
191 // = 0x59,
192
193 EX_WireTracepoint = 0x5A, // Trace point. Only observed in the editor, otherwise it behaves like EX_Nothing.
194
195 EX_SkipOffsetConst = 0x5B, // A CodeSizeSkipOffset constant
196
197 EX_AddMulticastDelegate = 0x5C, // Adds a delegate to a multicast delegate's targets
198
199 EX_ClearMulticastDelegate = 0x5D, // Clears all delegates in a multicast target
200
201 EX_Tracepoint = 0x5E, // Trace point. Only observed in the editor, otherwise it behaves like EX_Nothing.
202
203 EX_LetObj = 0x5F, // assign to any object ref pointer
204
205 EX_LetWeakObjPtr = 0x60, // assign to a weak object pointer
206
207 EX_BindDelegate = 0x61, // bind object and name to delegate
208
209 EX_RemoveMulticastDelegate = 0x62, // Remove a delegate from a multicast delegate's targets
210
211 EX_CallMulticastDelegate = 0x63, // Call multicast delegate
212
213 EX_LetValueOnPersistentFrame = 0x64,
214
215 EX_ArrayConst = 0x65,
216
217 EX_EndArrayConst = 0x66,
218
219 EX_AssetConst = 0x67,
220
221 EX_CallMath = 0x68, // static pure function from on local call space
222
223 EX_SwitchValue = 0x69,
224
225 EX_InstrumentationEvent = 0x6A, // Instrumentation event
226
227 EX_ArrayGetByRef = 0x6B,
228
229 EX_Max = 0x100,
230
231 };
拷贝类默认对象(CDO)属性
使用一个特殊的函数CopyPropertiesForUnrelatedObjects(),编译器把类旧CDO中的值拷贝到新的CDO中。属性是通过带标记的序列化来拷贝的,只要名字是一致的,它们就应该被正确的复制。CDO中的组件会重新实例化。
重新实例化
由于类可能已经改变大小或者属性已经添加或删除了,编译器需要重新实例化该类实例化的所有对象。它使用TOjbectIterator来找到该类的实例,创建一个新的,并且使用CopyPropertiesForUnrelatedObjects()函数来把旧实例的数据拷贝到新的实例。详细信息参照FBlueprintCompileReinstancer类。
编译实例学习
我在蓝图里面新建了一个继承自Actor的NewBlueprint,它有一个变量StringTest并且实现了一个BeginPlay 事件和一个FunctionTest()函数,这个函数有一个局部变量LocalStringTest。
下面分别是BeginPlay和FunctionTest的定义:


为了看到编译的结果,我们需要修改BaseEngine.ini中的设置把CompileDisplaysBinaryBackend设置为true,如果要显示生成的cpp文件,也可以把CompileDisplaysTextBackend设置为true。注意需要重新启动编辑器。点击编译后在OutputLog中得到的结果如下代码所示:
1 BlueprintLog: New page: Compile NewBlueprint 2 3 LogK2Compiler: [function ExecuteUbergraph_NewBlueprint]: 4 5 Label_0x0: 6 7 $4E: Computed Jump, offset specified by expression: 8 9 $0: Local variable named EntryPoint 10 11 Label_0xA: 12 13 $5E: .. debug site .. 14 15 Label_0xB: 16 17 $5A: .. wire debug site .. 18 19 Label_0xC: 20 21 $5E: .. debug site .. 22 23 Label_0xD: 24 25 $1B: Virtual Function named FunctionTest 26 27 $0: Local variable named CallFunc_FunctionTest_OutNewString 28 29 $16: EX_EndFunctionParms 30 31 Label_0x24: 32 33 $5A: .. wire debug site .. 34 35 Label_0x25: 36 37 $5E: .. debug site .. 38 39 Label_0x26: 40 41 $19: Context 42 43 ObjectExpression: 44 45 $20: EX_ObjectConst (000000003022A100:KismetSystemLibrary /Script/Engine.Default__KismetSystemLibrary) 46 47 Skip Bytes: 0x3D 48 49 R-Value Property: (null) 50 51 ContextExpression: 52 53 $1C: Final Function (stack node KismetSystemLibrary::PrintString) 54 55 $17: EX_Self 56 57 $0: Local variable named CallFunc_FunctionTest_OutNewString 58 59 $28: EX_False 60 61 $27: EX_True 62 63 $2F: literal struct LinearColor (serialized size: 16) 64 65 $1E: literal float 0.000000 66 67 $1E: literal float 0.660000 68 69 $1E: literal float 1.000000 70 71 $1E: literal float 1.000000 72 73 $30: EX_EndStructConst 74 75 $1E: literal float 2.000000 76 77 $16: EX_EndFunctionParms 78 79 Label_0x79: 80 81 $5A: .. wire debug site .. 82 83 Label_0x7A: 84 85 $4: Return expression 86 87 $B: EX_Nothing 88 89 Label_0x7C: 90 91 $53: EX_EndOfScript 92 93 LogK2Compiler: [function ReceiveBeginPlay]: 94 95 Label_0x0: 96 97 $1B: Virtual Function named ExecuteUbergraph_NewBlueprint 98 99 $1D: literal int32 10 100 101 $16: EX_EndFunctionParms 102 103 Label_0x13: 104 105 $4: Return expression 106 107 $B: EX_Nothing 108 109 Label_0x15: 110 111 $53: EX_EndOfScript 112 113 LogK2Compiler: [function UserConstructionScript]: 114 115 Label_0x0: 116 117 $5E: .. debug site .. 118 119 Label_0x1: 120 121 $5A: .. wire debug site .. 122 123 Label_0x2: 124 125 $4: Return expression 126 127 $B: EX_Nothing 128 129 Label_0x4: 130 131 $53: EX_EndOfScript 132 133 LogK2Compiler: [function FunctionTest]: 134 135 Label_0x0: 136 137 $5E: .. debug site .. 138 139 Label_0x1: 140 141 $5A: .. wire debug site .. 142 143 Label_0x2: 144 145 $5E: .. debug site .. 146 147 Label_0x3: 148 149 $F: Let (Variable = Expression) 150 151 Variable: 152 153 $0: Local variable named LocalStringTest 154 155 Expression: 156 157 $1F: literal ansi string "Bluepirnt Test: " 158 159 Label_0x27: 160 161 $5A: .. wire debug site .. 162 163 Label_0x28: 164 165 $F: Let (Variable = Expression) 166 167 Variable: 168 169 $0: Local variable named CallFunc_Concat_StrStr_ReturnValue 170 171 Expression: 172 173 $19: Context 174 175 ObjectExpression: 176 177 $20: EX_ObjectConst (0000000030229B00:KismetStringLibrary /Script/Engine.Default__KismetStringLibrary) 178 179 Skip Bytes: 0x1C 180 181 R-Value Property: CallFunc_Concat_StrStr_ReturnValue 182 183 ContextExpression: 184 185 $1C: Final Function (stack node KismetStringLibrary::Concat_StrStr) 186 187 $0: Local variable named LocalStringTest 188 189 $1: Instance variable named StringTest 190 191 $16: EX_EndFunctionParms 192 193 Label_0x6C: 194 195 $5E: .. debug site .. 196 197 Label_0x6D: 198 199 $F: Let (Variable = Expression) 200 201 Variable: 202 203 $48: Local out variable named OutNewString 204 205 Expression: 206 207 $0: Local variable named CallFunc_Concat_StrStr_ReturnValue 208 209 Label_0x88: 210 211 $5A: .. wire debug site .. 212 213 Label_0x89: 214 215 $4: Return expression 216 217 $B: EX_Nothing 218 219 Label_0x8B: 220 221 $53: EX_EndOfScript
有个需要说明的地方就是,我们可以看到事件ReceiveBeginPlay,如上面我们所说,它具体并没有做什么事情,整个函数的指令被放到了function ExecuteUbergraph_NewBlueprint中,而它所做的事情是调用了 Virtual Function named ExecuteUbergraph_NewBlueprint 并传递了一个int32的值,这个值是在ExecuteUbergraph_NewBlueprint处的偏移值,而在ExecuteUbergraph_NewBlueprint一开始就根据传进来的偏移值进行了无条件jump跳转到相应的位置进行程序的执行。
总结
至此,我们我们对蓝图的编译过程有了一个基本的了解,粗略地讲解了蓝图是如何从我们编辑的结果最终编译的过程,接下来的文章我们将介绍虚幻4中蓝图虚拟机的实现,敬请期待。当然由于本人理解能力有限,里面难免有错误的地方,如读者发现还请指正。
参考文档
- https://docs.unrealengine.com/latest/INT/Engine/Blueprints/TechnicalGuide/Compiler/index.html
- http://www.cnblogs.com/ghl_carmack/p/5804737.html
- 脚本语言入门书籍 Game Scripting Mastery
- http://blog.csdn.net/tangl_99/article/details/5600
来源:https://www.cnblogs.com/ghl_carmack/p/6014655.html