虚幻4蓝图虚拟机剖析

前言

这里,我们打算对虚幻4 中蓝图虚拟机的实现做一个大概的讲解,如果对其它的脚本语言的实现有比较清楚的认识,理解起来会容易很多,我们先会对相关术语进行一个简单的介绍,然后会对蓝图虚拟机的实现做一个讲解。

术语

编程语言一般分为编译语言和解释型语言。

编译型语言

程序在执行之前需要一个专门的编译过程,把程序编译成 为机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++、Delphi等.

解释性语言

编写的程序不进行预先编译,以文本方式存储程序代码。在发布程序时,看起来省了道编译工序。但是,在运行程序的时候,解释性语言必须先解释再运行。

然而关于Java、C#等是否为解释型语言存在争议,因为它们主流的实现并不是直接解释执行的,而是也编译成字节码,然后再运行在jvm等虚拟机上的。

UE4中蓝图的实现更像是lua的实现方式,它并不能独立运行,而是作为一种嵌入宿主语言的一种扩展脚本,lua可以直接解释执行,也可以编译成字节码并保存到磁盘上,下次调用可以直接加载编译好的字节码执行。

什么是虚拟机

虚拟机最初由波佩克[a]与戈德堡定义为有效的、独立的真实机器的副本。当前包括跟任何真实机器无关的虚拟机。虚拟机根据它们的运用和与直接机器的相关性分为两大类。系统虚拟机(如VirtualBox)提供一个可以运行完整操作系统的完整系统平台。相反的,程序虚拟机(如Java JVM)为运行单个计算机程序设计,这意謂它支持单个进程。虚拟机的一个本质特点是运行在虚拟机上的软件被局限在虚拟机提供的资源里——它不能超出虚拟世界。

而这里我们主要关心的是程序虚拟机,VM既然被称为"机器",一般认为输入是满足某种指令集架构(instruction set architecture,ISA)的指令序列,中间转换为目标ISA的指令序列并加以执行,输出为程序的执行结果的,就是VM。源与目标ISA可以是同一种,这是所谓same-ISA VM。

分类

虚拟机实现分为基于寄存器的虚拟机和基于栈的虚拟机。

三地址指令

a = b + c;

如果把它变成这种形式:

add a, b, c

那看起来就更像机器指令了,对吧?这种就是所谓"三地址指令"(3-address instruction),一般形式为:

op dest, src1, src2

许多操作都是二元运算+赋值。三地址指令正好可以指定两个源和一个目标,能非常灵活的支持二元操作与赋值的组合。ARM处理器的主要指令集就是三地址形式的。

二地址指令

a += b;

变成:

add a, b

这就是所谓"二地址指令",一般形式为:

op dest, src

它要支持二元操作,就只能把其中一个源同时也作为目标。上面的add a, b在执行过后,就会破坏a原有的值,而b的值保持不变。x86系列的处理器就是二地址形式的。

一地址指令

显然,指令集可以是任意"n地址"的,n属于自然数。那么一地址形式的指令集是怎样的呢?

想像一下这样一组指令序列:

add 5

sub 3

这只指定了操作的源,那目标是什么?一般来说,这种运算的目标是被称为"累加器"(accumulator)的专用寄存器,所有运算都靠更新累加器的状态来完成。那么上面两条指令用C来写就类似:

C代码 收藏代码

acc += 5;

acc -= 3;

只不过acc是"隐藏"的目标。基于累加器的架构近来比较少见了,在很老的机器上繁荣过一段时间。

零地址指令

那"n地址"的n如果是0的话呢?

看这样一段Java字节码:

Java bytecode代码 收藏代码

iconst_1

iconst_2

iadd

istore_0

注意那个iadd(表示整型加法)指令并没有任何参数。连源都无法指定了,零地址指令有什么用??

零地址意味着源与目标都是隐含参数,其实现依赖于一种常见的数据结构——没错,就是栈。上面的iconst_1、iconst_2两条指令,分别向一个叫做"求值栈"(evaluation stack,也叫做operand stack"操作数栈"或者expression stack"表达式栈")的地方压入整型常量1、2。iadd指令则从求值栈顶弹出2个值,将值相加,然后把结果压回到栈顶。istore_0指令从求值栈顶弹出一个值,并将值保存到局部变量区的第一个位置(slot 0)。

零地址形式的指令集一般就是通过"基于栈的架构"来实现的。请一定要注意,这个栈是指"求值栈",而不是与系统调用栈(system call stack,或者就叫system stack)。千万别弄混了。有些虚拟机把求值栈实现在系统调用栈上,但两者概念上不是一个东西。

由于指令的源与目标都是隐含的,零地址指令的"密度"可以非常高——可以用更少空间放下更多条指令。因此在空间紧缺的环境中,零地址指令是种可取的设计。但零地址指令要完成一件事情,一般会比二地址或者三地址指令许多更多条指令。上面Java字节码做的加法,如果用x86指令两条就能完成了:

mov eax, 1

add eax, 2

基于栈与基于寄存器结构的区别

  1. 保存临时值的位置不同
  • 基于栈:将临时值保存在求值栈上。
  • 基于寄存器:将临时值保存在寄存器中。
  1. 代码所占体积不同
  • 基于栈:代码紧凑,体积小,但所需要的代码条件多
  • 基于寄存器:代码相对大些,但所需要的代码条件少

基于栈中的"栈"指的是"求值栈",JVM中"求值栈"被称为"操作数栈"。

栈帧

栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。从逻辑上讲,栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等。

蓝图虚拟机的实现

前面我们已经简单得介绍了虚拟机的相关术语,接下来我们来具体讲解下虚幻4中蓝图虚拟机的实现。

字节码

虚拟机的字节码在Script.h文件中,这里我们把它全部列出来,因为是专用的脚本语言,所以它里面会有一些特殊的字节码,如代理相关的代码(EX_BindDelegate、EX_AddMulticastDelegate),当然常用的语句也是有的,比如赋值、无条件跳转指令、条件跳转指令、switch等。

  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 };

栈帧

在Stack.h中我们可以找到FFrame的定义,虽然它定义的是一个结构体,但是执行当前代码的逻辑是封装在这里面的。下面让我们看一下它的数据成员:

 1   // Variables.
 2
 3     UFunction* Node;
 4
 5     UObject* Object;
 6
 7     uint8* Code;
 8
 9     uint8* Locals;
10
11
12
13     UProperty* MostRecentProperty;
14
15     uint8* MostRecentPropertyAddress;
16
17
18
19     /** The execution flow stack for compiled Kismet code */
20
21     FlowStackType FlowStack;
22
23
24
25     /** Previous frame on the stack */
26
27     FFrame* PreviousFrame;
28
29
30
31     /** contains information on any out parameters */
32
33     FOutParmRec* OutParms;
34
35
36
37     /** If a class is compiled in then this is set to the property chain for compiled-in functions. In that case, we follow the links to setup the args instead of executing by code. */
38
39     UField* PropertyChainForCompiledIn;
40
41
42
43     /** Currently executed native function */
44
45     UFunction* CurrentNativeFunction;
46
47
48
49     bool bArrayContextFailed;

我们可以看到,它里面保存了当前执行的脚本函数,执行该脚本的UObject,当前代码的执行位置,局部变量,上一个栈帧,调用返回的参数(不是返回值),当前执行的原生函数等。而调用函数的返回值是放在了函数调用之前保存,调用结束后再恢复。大致如下所示:

1 uint8 * SaveCode = Stack.Code;
2
3 // Call function
4
5 ….
6
7 Stack.Code = SaveCode

下面我们列出FFrame中跟执行相关的重要函数:

  1     // Functions.
  2
  3     COREUOBJECT_API void Step( UObject* Context, RESULT_DECL );
  4
  5
  6
  7     /** Replacement for Step that uses an explicitly specified property to unpack arguments **/
  8
  9     COREUOBJECT_API void StepExplicitProperty(void*const Result, UProperty* Property);
 10
 11
 12
 13     /** Replacement for Step that checks the for byte code, and if none exists, then PropertyChainForCompiledIn is used. Also, makes an effort to verify that the params are in the correct order and the types are compatible. **/
 14
 15     template<class TProperty>
 16
 17     FORCEINLINE_DEBUGGABLE void StepCompiledIn(void*const Result);
 18
 19
 20
 21     /** Replacement for Step that checks the for byte code, and if none exists, then PropertyChainForCompiledIn is used. Also, makes an effort to verify that the params are in the correct order and the types are compatible. **/
 22
 23     template<class TProperty, typename TNativeType>
 24
 25     FORCEINLINE_DEBUGGABLE TNativeType& StepCompiledInRef(void*const TemporaryBuffer);
 26
 27
 28
 29     COREUOBJECT_API virtual void Serialize( const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category ) override;
 30
 31
 32
 33     COREUOBJECT_API static void KismetExecutionMessage(const TCHAR* Message, ELogVerbosity::Type Verbosity, FName WarningId = FName());
 34
 35
 36
 37     /** Returns the current script op code */
 38
 39     const uint8 PeekCode() const { return *Code; }
 40
 41
 42
 43     /** Skips over the number of op codes specified by NumOps */
 44
 45     void SkipCode(const int32 NumOps) { Code += NumOps; }
 46
 47
 48
 49     template<typename TNumericType>
 50
 51     TNumericType ReadInt();
 52
 53     float ReadFloat();
 54
 55     FName ReadName();
 56
 57     UObject* ReadObject();
 58
 59     int32 ReadWord();
 60
 61     UProperty* ReadProperty();
 62
 63
 64
 65     /** May return null */
 66
 67     UProperty* ReadPropertyUnchecked();
 68
 69
 70
 71     /**
 72
 73      * Reads a value from the bytestream, which represents the number of bytes to advance
 74
 75      * the code pointer for certain expressions.
 76
 77      *
 78
 79      * @param    ExpressionField        receives a pointer to the field representing the expression; used by various execs
 80
 81      *                                to drive VM logic
 82
 83      */
 84
 85     CodeSkipSizeType ReadCodeSkipCount();
 86
 87
 88
 89     /**
 90
 91      * Reads a value from the bytestream which represents the number of bytes that should be zero‘d out if a NULL context
 92
 93      * is encountered
 94
 95      *
 96
 97      * @param    ExpressionField        receives a pointer to the field representing the expression; used by various execs
 98
 99      *                                to drive VM logic
100
101      */
102
103     VariableSizeType ReadVariableSize(UProperty** ExpressionField);

像ReadInt()、ReadFloat()、ReadObject()等这些函数,我们看到它的名字就知道它是做什么的,就是从代码中读取相应的int、float、UObject等。这里我们主要说下Step()函数,它的代码如下所示:

1 void FFrame::Step(UObject *Context, RESULT_DECL)
2
3 {
4
5     int32 B = *Code++;
6
7     (Context->*GNatives[B])(*this,RESULT_PARAM);
8
9 }

可以看到,它的主要作用就是取出指令,然后在原生函数数组中找到对应的函数去执行。

字节码对应函数

前面我们列出了所有的虚拟机的所有字节码,那么对应每个字节码具体执行部分的代码在哪里呢,具体可以到ScriptCore.cpp中查找定义,我们可以看到每个字节码对应的原生函数都在GNatives和GCasts里面:

它们的声明如下:

1 /** The type of a native function callable by script */
2
3 typedef void (UObject::*Native)( FFrame& TheStack, RESULT_DECL );
4
5 Native GCasts[];
6
7 Native GNatives[EX_Max];

这样它都会对每一个原生函数调用一下注册方法,通过IMPLEMENT_VM_FUNCTION和IMPLEMENT_CAST_FUNCTION宏实现。

具体代码如下图所示:

 1 #define IMPLEMENT_FUNCTION(cls,func)  2
 3     static FNativeFunctionRegistrar cls##func##Registar(cls::StaticClass(),#func,(Native)&cls::func);
 4
 5
 6
 7 #define IMPLEMENT_CAST_FUNCTION(cls, CastIndex, func)  8
 9     IMPLEMENT_FUNCTION(cls, func); 10
11     static uint8 cls##func##CastTemp = GRegisterCast( CastIndex, (Native)&cls::func );
12
13
14
15 #define IMPLEMENT_VM_FUNCTION(BytecodeIndex, func) 16
17     IMPLEMENT_FUNCTION(UObject, func) 18
19     static uint8 UObject##func##BytecodeTemp = GRegisterNative( BytecodeIndex, (Native)&UObject::func );

可以看到,它是定义了一个全局静态对象,这样就会在程序的main函数执行前就已经把函数放在数组中对应的位置了,这样在虚拟机执行时就可以直接调用到对应的原生函数了。

执行流程

我们前面讲蓝图的时候讲过蓝图如何跟C++交互,包括蓝图调用C++代码,以及从C++代码调用到蓝图里面去。

C++调用蓝图函数

 1 UFUNCTION(BlueprintImplementableEvent, Category = "AReflectionStudyGameMode")
 2
 3 void ImplementableFuncTest();
 4
 5
 6
 7 void AReflectionStudyGameMode::ImplementableFuncTest()
 8
 9 {
10
11 ProcessEvent(FindFunctionChecked(REFLECTIONSTUDY_ImplementableFuncTest),NULL);
12
13 }

因为我们这个函数没有参数,所有ProcessEvent中传了一个NULL,如果是有参数和返回值等,那么UHT会自动生成一个结构体用于存储参数和返回值等,这样当在C++里面调用函数时,就会去找REFLECTIONSTUDY_ImplementableFuncTest这个名字对应的蓝图UFunction,如果找到那么就会调用ProcessEvent来做进一步的处理。

ProcessEvent流程

蓝图调用C++函数

 1 UFUNCTION(BlueprintCallable, Category = "AReflectionStudyGameMode")
 2
 3 void CallableFuncTest();
 4
 5
 6
 7 DECLARE_FUNCTION(execCallableFuncTest)  8
 9 { 10
11 P_FINISH; 12
13 P_NATIVE_BEGIN; 14
15 this->CallableFuncTest(); 16
17 P_NATIVE_END; 18
19 }

如果是通过蓝图调用的C++函数,那么UHT会生成如上的代码,并且如果有参数的话,会调用P_GET_UBOOL等来获取对应的参数,如果有返回值的话也会将返回值赋值。

总结

至此,加上前面我们对蓝图编译的剖析,加上蓝图虚拟机的讲解,我们已经对蓝图的实现原理有一个比较深入的了解,本文并没有对蓝图的前身unrealscript进行详细的讲解。有了这个比较深入的认识后(如果想要有深刻的认识,必须自己去看代码),相信大家在设计蓝图时会更游刃有余。当然如果有错误的地方也请大家指正,欢迎大家踊跃讨论。接下来可能会把重心放到虚幻4渲染相关的模块上,包括渲染API跨平台相关,多线程渲染,渲染流程,以及渲染算法上面,可能中间也会穿插一些其他的模块(比如动画、AI等),欢迎大家持续关注,如果你有想提前了解的章节,也欢迎在下面留言,我可能会根据大家的留言来做优先级调整。

参考文章

  1. https://www.usenix.org/legacy/events/vee05/full_papers/p153-yunhe.pdf
  2. http://rednaxelafx.iteye.com/blog/492667
  3. http://www.zhihu.com/question/19608553
  4. https://zh.wikipedia.org/wiki/%E8%99%9B%E6%93%AC%E6%A9%9F%E5%99%A8
  5. Java Program in Action 莫枢
时间: 2024-10-07 11:13:24

虚幻4蓝图虚拟机剖析的相关文章

虚幻4蓝图编译剖析(三)

编译 上面基本的术语已经介绍完了,下面我们进入来进入蓝图编译过程分析.蓝图的编译过程都在FKismetCompilerContext::Compile()函数中.它根据编译的类型不同(上文编译选项中提到的只编译Skeleton.只生成字节码.只生成cpp代码等等)会走不同的分支.我们这里以完全编译来讲解.此处为大概的流程,若想看详细的流程,请参照流程图以及代码. 清除类 类是就地编译的,这意味着同一个UBlueprintGeneratedClass在每次编译的时候都会被清理,并且会重复利用,这样

虚幻4蓝图编译剖析(一)

前言 虚幻引擎中的 蓝图 - 可视化脚本系统 是一个完整的游戏脚本系统, 其理念是,在虚幻编辑器中,使用基于节点的界面创建游戏可玩性元素. 和其他一些常见的脚本语言一样,蓝图的用法也是通过定义在引擎中的面向对象的类或者对象. 在使用虚幻 4 的过程中,常常会遇到在蓝图中定义的对象,并且这类对象常常也会被直接称为"蓝图(Blueprint)".本文从代码的层面讲解虚幻引擎中蓝图的编译(包括编辑部分的代码,使用的版本是UE4 4.13.0),本文假设已经对蓝图已经有一个比较清楚的了解. 编

虚幻4蓝图编译剖析(二)

虚幻4编译相关术语和类图 虚幻引擎中的蓝图编译跟常规的程序编译多少是有一些不同的地方,但是基本原理是相通的.我们以普通的类蓝图为例,一个类中包含多个图,每个图中又可以包含一些子图.一个图会包含很多的节点(UEdGraphNode),每个节点可以包含若干引脚(UEdGraphPin)用来连接两个节点.节点又分为执行节点和纯节点(Pure node,上面没有执行引脚).还有一个模式类(UEdGraphSchema)用于验证语法是否正确等.类图如下所示: 图(UEdGraph) 虚幻4中许多其它的也是

虚幻4蓝图跑酷游戏制作公开课

#虚幻4# 蓝图跑酷游戏制作公开课,持续更新中. 油管地址:http://t.cn/Rczx3Jv 国内地址:http://t.cn/Rczx3JP 本课程是免费课程,赞助随意,一杯咖啡钱即可,几杯也行,慢慢喝--

虚幻4蓝图快速入门(一)

蓝图快速入门 序言 本文依据官方教程总结而来,只是带你对蓝图有一个快速的认识,如果想对蓝图有一个比较深入的了解,那么可以看官方的视频或者是做一些小项目练手,如果你有编程经验的话,上手还是很容易的. 蓝图快速入门 什么是蓝图 虚幻引擎中的蓝图可视化系统是一个完整的游戏脚本系统,其理念是使用基于节点的界面从虚幻编辑器中创建游戏可玩性元素,该系统非常灵活且非常强大,因为它为设计人员提供了一般仅供程序员使用的所有概念及工具.它是一种特殊类型的资源,为关卡设计师和游戏开发人员提供了一种在编辑器中快速创建及

虚幻4蓝图快速入门(四)

蓝图跟C++交互 概述 蓝图可以继承C++类,从而使得程序员可以在代码中创建新的游戏性类,而关卡设计人员可以使用蓝图来继承该类并对其进行修改. 有很多种修饰符可以改变C++类和蓝图系统间交互方式,其中某些修饰符会在本示例中突出介绍. 可以通过查看以下内容来快速了解: 虚幻引擎快速入门视频教程第五章,见引用[1] 官方文档 类设置 在类设置的第一部分中,使用C++类向导创建一个名称为LightSwitchBoth 的类. LightSwitchBoth类中的大部分代码设置都和 仅使用C++的Lig

虚幻4蓝图快速入门(三)

数学表达式节点 概述 要想创建一个数学表达式节点,请右击图表并从关联菜单中选择 Add Math Expression(添加数学表达式)... . 数学表达式节点就像一个合并的图表.它是一个独立的节点,您可以双击它来打开构成其功能的子图表. 最初,该名称/表达式是空的.任何时候,当您重命名该节点时,都将会解析新表达式并生成新的子图表. 变量 变量命名非常灵活,但是记住以下几点非常重要: 变量名称本身可以包含数字,但是不能以数字开头. 变量名称不能和隐藏的蓝图变量名称一样. 确保您正在使用正确的变

虚幻4蓝图快速入门(二)

蓝图用户指南 由于蓝图就是个可视化的脚本系统,那么一个程序语言中的基本概念也就都存在.下面我们简单来介绍下蓝图中的一些基本概念. 变量 概述 Variables(变量) 是存放一个值或引用世界中的一个Object或Actor的属性.这些用户界面 内部访问,或者通过设置使得可以在外部进行访问, 以便应用放置在关卡中的蓝图实例的设计人员可以修改它们的 值. 变量显示为圆角方框,方框内包含了变量的名称: 变量类型 您可以创建各种类型的变量,包括数据类型的变量(比如布尔型.整型及浮点型)及用于存放类似于

虚幻4垃圾回收剖析

上一个系列的文章我们已经对虚幻4中的反射实现原理进行了一个简单得讲解,反射的用途非常多,其中一个就是用来做垃圾回收用的,我们这个系列就对虚幻4中的垃圾回收机制做一个讲解.注:本系列文章对应的虚幻4版本是4.14.1 垃圾回收 在计算机科学中,垃圾回收(garbage collection, 缩写GC)是一种自动的内存管理机制.当一个电脑上的动态内存不需要时,就应该予以释放,这种自动内存的资源管理,称为垃圾回收.垃圾回收可以减少程序员的负担,也能减少程序员犯错的机会.最早起源于LISP语言.目前许