2019-12-07 21:49:40 weixin_44332254 阅读数 12
  • 从这里开始虚幻4-第2辑-蓝图 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2145 人正在学习 去看看 杨石兴

最近开始摸索Unreal的蓝图基础使用,一些基本的操作大家可以在官方网站中的教程中找到,所以我们直接跳到事件图表上来讲。
首先,Unreal有丰富的注释功能,按住键盘C可以创建出注释区域,在右侧的“细节”面板我们可以轻松调整颜色以及气泡的出现。

节点

见过一些别人编辑好的蓝图的同学们都知道,蓝图中有错综复杂的连线,这些连线从一个地方出来,到另一个地方去,控制了流程和数据的输入输出。就像是交通线,每个城市就相当于“节点”

节点类型
事件(Events)、自定义事件、函数调用、事件调度器、流程控制、数组节点、时间轴、数学表达式节点、随机流、注释。这些都属于节点类型,丰富的节点类型可以帮助我们在小型的游戏开发中基本不需要编写自己的C++代码,实现一些功能,但是同时者考验来蓝图编写者的逻辑思维能力。

事件
在蓝图中可以定义各种事件,创建蓝图时默认就会为我们提供三种事件:BeginPlay Tick以及Overlap。对应着我们游戏开始,帧调用以及碰撞触发事件。 在一个蓝图中,事件的种类可以有多个,但是同种类型的事件只能有一个。
另外,我们也可以自定义事件,Add Event一个事件,然后我们在细节面板可以修改自定义事件的名字,定义各种事件的成员变量。

函数调用
函数的调用是蓝图中同样重要的一环,函数调用可以简单分为一般函数调用,其他函数调用和纯函数调用。
所谓一般函数调用,是可以在蓝图中形成的操作,同属于目标Actor或对象的函数相对应 。
其他函数调用,是属于除了该蓝图之外的其他对象或者Actor的函数。
纯函数调用,是可以执行的特殊动作,它不会影响世界或者世界中的对象。比如输出一个属性值,进行数学运算等。

流程控制
switch:提供了各种switch判断的类型,也可以自定义枚举类型在这里判断。
branch:相当于if else语句
Do N:接受条件执行,但是有次数限制,达到次数上限后,再次接受相同条件无法执行后续节点操作。但是可以通过重置使次数归0 .
DoOnce:同上,但是只能执行一次
DoOnceMultiInput:可以根据不同情况执行不同的操作,但是如果想要再次执行,需要Reset。
FlipFlop:交替执行A和B后面的内容。
ForLoop:for循环
ForLoopWithBreak:可以在一定条件下终止循环,执行相应的语句。
Gate:门,顾名思义。可以设置Enter的条件和Exit的执行语句,可以设置open的条件,close的条件,或者用toggle使其自动open和close·······
MultiGate:可以又多种输出的开关门,可以设置开始和重置,可以选择是否随机或者循环,可以从只当index的输出开始
Sequence:按顺序一次执行,但是值得注意的是,即使写了Delay,Sequence也会直接执行后面的语句

变量

存放一个值或者引用世界中一个Object或Actor的属性。
变量类型:
在这里插入图片描述
在这里插入图片描述

详细介绍:

布尔值(bool):小案例:利用不布尔值设置一个point light的intensity,按下键盘一个键后,如果灯是打开则关闭,如果关闭则打开。
易犯错误:将一个布尔值取反后,忘记重新把这个取反后的值设置回这个变量中,导致只能发生一次变化。
字节(byte):学过一定计算机理论的知道,1字节是8位,在UE中存储范围是0-255。在右侧细节栏中可以设置UI中滑动的范围以及值的取值范围,后者控制前者。
整型和64位整型:整型是32位,两者仅仅范围不同
浮点型(float):范围很大,案例:做一个四舍五入的功能(利用浮点型转整型操作)。
Name,String以及Text:Name是轻量级的字符串,经常被用于对象的命名 string是我们通常用的字符串,支持字符串的拼接,查找等其他语言中都存在的操作 Text是大段文本,如果游戏中出现大段对话,基本我们就使用Text,这样会对我们的本地化非常有帮助。
Vector:引擎中很多属性例如位置,旋转,缩放都是使用Vector。案例:使用Vector修改物体的位置和缩放。
Rotator:和Vector类似,可以用来定义一个旋转角度。
Transform:Transform即物体的位置,旋转和缩放,第一个和第三个是Vector类型,第二个是Rotator类型。案例:获取物体的Transform中的旋转进行输出。

Enum枚举类型:我们可以在内容浏览器中新建一个自己的枚举类型,为其设置各种情况以及解释
Structure结构体:我们可以在内容浏览器中新建一个结构体,类似于各种编程语言中的struct
综合案例:创建一个学生结构体,属性为姓名,年龄,以及学校(利用枚举类型),在蓝图中实现这样的功能:根据学生年龄的大小,输出“姓名”+是否成年+“(学校类型)”。

数组节点:
由于本篇文章是基于有一定其他编程语言经验的人编写,所以数组方面可能不会那么详尽的去介绍。
在这里插入图片描述
在这个地方可以修改变量的类型,有普通变量,数组,集以及字典。

数组中的方法:
在这里插入图片描述
很多方法都是其他语言中存在的一些常用的方法,大多都属于增删改查,具体如何使用各位客官可以自己摸索。

集合:
接下来就是我们的第三种数据存储容器,集合
在这里插入图片描述
在C#中,我们有数组,列表,字典,哈希表,栈和队列等数据结构,集合也是一种类数据结构容器,在初等数学中我们应该学过集合相关的知识。
如果硬要对比的话,集合就好比一个键值对相同的字典。
在蓝图中,可以对集合进行如上图的操作。其中也包含了最常用的差并交三个基本操作。

地图:
第四种数据存储容器:map
这个就很熟悉了,有的编程语言中它叫map,有的中是Dictionary,本质上就是一个键值对,而且是一种引用类型的键值对;与之相对的是,哈希表是一种值类型的键值对。
在这里插入图片描述
上面是map中一些常用的方法,有一定数据结构知识的客官们应该能轻松应用这些方法。

函数(Function)

在这里插入图片描述
在上图中,我们已经大概了解了图标和变量的含义,那么接下来是函数。
在这里插入图片描述
可以覆盖,但由于我们还没有继承的子类或者父类,我们就先新建一个函数
在这里插入图片描述
点击新建的函数,可以进入我们的函数界面,在右侧的细节面板有我们的一些函数相关的设置,例如一些注释,访问方式(public protected private) 是否为纯函数 在输入和输出中,我们可以定义相应的函数的输入和输出数据流。

实操训练:对一个数组进行排序
unreal蓝图中实现数组的排序是非常消耗性能且复杂的。

宏:
宏跟函数非常类似,可以定义输出和输出,而它比函数多了一个功能,它可以在右侧的细节面板设置多个输入和输出引脚,有点类似于我们的分支,具体如何使用到之后继续总结。

数学节点–蓝图中的math类
在这里插入图片描述
如图所示,在蓝图中搜索math,会出现很多一些蓝图中的数学节点,这些节点提供了我们对一些数据类型的基本的操作。
数学节点也包括对transformation的操作,另外,我们的Random节点也归在数学节点里,不再做进一步的介绍,请各位客官尝试使用一下各种数学函数,灵活运用到自己的项目中。

时间轴:
Timeline为我们提供了一个基于时间的控制器,不仅仅是做动画,Timeline代表了一种在时间轴上进行各种操作的编程习惯。
对Timeline的介绍分为两部分,首先是Timeline的曲线编辑器:
在这里插入图片描述最上面一排,可以添加基于各种类型变量的曲线,设置曲线的时间,自动播放,循环等属性。在一个Timeline中可以定义多条曲线,都会在事件图表中显示出来。中间是我们的曲线编辑窗口,我们可以按shift去添加关键帧,用delete去删除关键帧。右键点击关键帧,可以设置关键帧在此处的状态是平滑,线性还是剧变等等状态。

然后是事件图表中的Timeline节点:
在这里插入图片描述
Play:播放
Play from Start:从头开始播放
Stop:停止
Reverse:从后往前播放
Reverse from End:从最后往前播放
Set New Time:为动画设置一个新的时间
Update:每帧执行
Finished:结束执行
Direction:播放是从后往前还是从前往后

Timeline在创建后,在左侧会以组件的形式出现,我们可以像对其他变量和组件一样对它进行get 和set
下面是关于Timeline节点的一些方法:
在这里插入图片描述

2019-04-27 10:59:52 Neil3D 阅读数 1132
  • 从这里开始虚幻4-第2辑-蓝图 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2145 人正在学习 去看看 杨石兴

Unreal的蓝图和C++一样,也是一种静态类型的编程语言,它又不像其他静态类型语言那样支持模板,有些时候就觉得很不方便。思考了一下这个问题。想要蓝图节点支持任意类型的参数,主要分为两种情况:

  • UObject派生类对象:那很简单了,使用基类指针作为参数就好,在C++里面可以Cast,或者取得对象的UClass,就可以根据反射信息做很多事了;
  • Struct类型,或者TArray<MyStruct>类型:这个是本文的重点。

其实说蓝图完全不支持“模板”也是不对的,引擎中其实已经有很多能够处理任意Struct或者TArray<MyStruct>类型的节点了!官方文档中把这种情况叫做参数“Wildcard”(通配符)。感谢Unreal开源,通过阅读源代码,加上一点实验,就能够搞清楚具体实现方法和背后的细节。

下面主要探讨使用UFUNCTION的CustomThunk描述符,实现自定义的Thunk函数;然后通过指定meta的CustomStructureParamArrayParm参数,来实现参数类型“通配符”!这中间的难点是:需要明确蓝图Stack的处理方式。Demo如下图所示:
在这里插入图片描述

在上图的Demo中:

  1. 自定义了一个蓝图Struct:MyStruct
  2. 使用C++实现了一个蓝图节点“Show Struct Fields”:可以接受任意UStruct的引用,具体类型可以由C++或者蓝图定义;
  3. 蓝图节点“Array Numeric Field Average”:可以接受任意类型的TArray<MyStruct>,并对数组中指定的数值型字段求平均;

完整的Demo工程可以从我的GitHub下载:https://github.com/neil3d/UnrealCookBook

实现蓝图功能节点的几种方式

在Unreal开发中可以使用C++对蓝图进行扩展,生成Unreal蓝图节点最方便的方法就是写一个UFUNCTION,无论是定义在UBlueprintFunctionLibrary派生类里面的static函数,还是定义在UObject、AActor派生类里面的类成员函数,只要加上UFUNCTION宏修饰,并在宏里面添加BlueprintCallable标识符,就可以自动完成蓝图编辑节点、蓝图节点执行调用的整个过程。不过,由于C++和蓝图都属于“静态类型”编程语言,这种形式编写的蓝图节点,所有的输入、输出参数的类型都必须是固定的,这样引擎才能自动处理蓝图虚拟机的栈。

先来总结一下C++实现蓝图节点的几种方式:

  1. UFUNCTION,上面已经说过了;
  2. 实现class UK2Node的派生类,这是最强大的方式,是对蓝图节点最深入的定制开发,如果你需要动态的添加、删除蓝图节点的针脚,就只能用这种方式了。例如我们常用的“Format Text”节点,可以根据输入字符串中的“{index}”来动态增加输入节点,输入节点的类型也是动态的,这个就是通过class UK2Node_FormatText这个类来实现的;
  3. 还有介于上面两者之间的一种方式,就是在UFUNCTION中使用“CustomThunk”标识,告诉UHT(Unreal Header Tool)不要生成默认的蓝图包装函数,而是由我们手工实现。这种方式,需要手工控制蓝图虚拟机的“栈”,但是不用处理蓝图编辑器UI部分,相对第2种来说代码量要少很多,相对第1种来说,又多了很多控制力;
  4. 另外,蓝图的“宏”–Macros,也可以实现自己的节点。

使用第3种方式,结合UFUNCTION的其它meta标识符,可以实现参数类型的“通配符”,就可以实现模板函数,也就是输入、输出参数可以处理多种数据类型,类似C++的泛型。这些meta标识符主要有:

  1. ArrayParm="Parameter1, Parameter2, ..":说明 BlueprintCallable 函数应使用一个Call Array Function节点,且列出的参数应被视为通配符数组属性;
  2. ArrayTypeDependentParams="Parameter":使用 ArrayParm 时,此说明符将指定一个参数,其将确定 ArrayParm 列表中所有参数的类型;
  3. CustomStructureParam="Parameter1, Parameter2, ..":列出的参数都会被视为通配符。

引擎源代码中,这种编程方式的典型的例子有:

  • 蓝图编辑器中的“Utilities”->“Array”菜单中的所有节点,他们可以处理任意的UStruct类型的数组。这些节点对应的源代码是:class UKismetArrayLibrary
  • class UDataTableFunctionLibrary::GetDataTableRowFromName(UDataTable* Table, FName RowName, FTableRowBase& OutRow)

详见官方文档:UFunctions

CustomThunk函数

如果在UFUNCTION宏里面指定了CustomThunk,那么UHT就不会自动生成这个函数的“thunk”,而需要开发者自己实现。这里的“thunk”是什么呢?我们看个例子。

我们来做个最简单的小试验,在工程中建立一个Blueprint Function Library,添加一个简单的UFUNCTION:

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"

UCLASS()
class MYBLUEPRINTNODES_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
public:
	UFUNCTION(BlueprintCallable)
	static int Sum(int a, int b);
};

然后在对应的cpp文件中,使用C++实现这个函数:

#include "MyBlueprintFunctionLibrary.h"

int UMyBlueprintFunctionLibrary::Sum(int a, int b) {
	return a + b;
}

项目build一下,然后你就可以在“Intermediate”目录找到这个"MyBlueprintFunctionLibrary.generated.h"文件。在这个文件里面,你可以找到这样一段代码:

    DECLARE_FUNCTION(execSum) \
	{ \
		P_GET_PROPERTY(UIntProperty,Z_Param_a); \
		P_GET_PROPERTY(UIntProperty,Z_Param_b); \
		P_FINISH; \
		P_NATIVE_BEGIN; \
		*(int32*)Z_Param__Result=UMyBlueprintFunctionLibrary::Sum(Z_Param_a,Z_Param_b); \
		P_NATIVE_END; \
	}

这段代码就是蓝图函数节点的thunk了!这段代码做了这样几件事:

  1. 声明了一个名为“execSum”的函数,函数的签名为:void func( UObject* Context, FFrame& Stack, RESULT_DECL )
  2. 使用P_GET_PROPERTY宏,从“FFrame& Stack”(也就是蓝图虚拟机的栈)中取出函数参数;
  3. 调用P_FINISH宏;
  4. 使用取出的这些参数调用我们实现的UMyBlueprintFunctionLibrary::Sum()函数;

“thunk”函数是一个包装,它完成的核心任务就是处理蓝图虚拟机的Stack,然后调用我们使用C++实现的函数。

我们还可以看一下UHT帮我们生成的另外一个文件:MyBlueprintFunctionLibrary.gen.cpp,在其中有这样一段代码:

void UMyBlueprintFunctionLibrary::StaticRegisterNativesUMyBlueprintFunctionLibrary()
	{
		UClass* Class = UMyBlueprintFunctionLibrary::StaticClass();
		static const FNameNativePtrPair Funcs[] = {
			{ "Sum", &UMyBlueprintFunctionLibrary::execSum },
		};
		FNativeFunctionRegistrar::RegisterFunctions(Class, Funcs, ARRAY_COUNT(Funcs));
	}

这段代码把刚才"MyBlueprintFunctionLibrary.generated.h"中声明的excSum函数注册到了UMyBlueprintFunctionLibrary::StaticClass()这个UClass对象之中,并指定它的名字为“Sum”,也就是我们原始C++代码中声明的函数名,也是在蓝图编辑器中显示的名字。

看清楚了什么是“thunk函数”,“CustomThunk函数”也就不言自明了。在UFUNCTION中指定“CustomThunk”标识符,就是告诉UHT,不要在.generated.h中生成DECLARE_FUNCTION那部分代码,这部分代码改由手写。为啥要抛弃自动生成,而手写呢?回到本文主题:要实现“参数类型通配符”(或者叫做“蓝图模板节点”),就必须手写thunk!

蓝图Stack探索

要实现自己的thunk函数,核心任务就是“准确的处理蓝图虚拟机的栈”,可惜的是官方并没有这方面的文档!下面我就把自己的一些探索记录下来,请大家指正。

以上面的int Sum(int a, int b)函数为例,thunk函数使用P_GET_PROPERTY宏从Stack取值,这个宏P_GET_PROPERTY(UIntProperty,Z_Param_a)展开之后的代码如下所示:

	UIntProperty::TCppType Z_Param_a = UIntProperty::GetDefaultPropertyValue();
	Stack.StepCompiledIn<UIntProperty>(&Z_Param_a);

其中UIntProperty派生自TProperty_Numeric<int32>UIntProperty::TCppType就是“int32”无疑!

我们还需要处理TArray<MyStruct>这样的数据,所以我们重点要看一下这种参数类型的栈处理。
假设我们有一个C++的UStruct:

USTRUCT(Blueprintable)
struct FMyStruct {
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FString Name;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	int Value;
};

类似这样一个UFUNCTION:

UFUNCTION(BlueprintCallable)
static void PrintMyStructArray(const TArray<FMyStruct>& MyStructArray);

则在.h中的thunk函数为:

DECLARE_FUNCTION(execPrintMyStructArray) \
	{ \
		P_GET_TARRAY_REF(FMyStruct,Z_Param_Out_MyStructArray); \
		P_FINISH; \
		P_NATIVE_BEGIN; \
		UMyBlueprintFunctionLibrary::PrintMyStructArray(Z_Param_Out_MyStructArray); \
		P_NATIVE_END; \
	} \

其中P_GET_TARRAY_REF(FMyStruct,Z_Param_Out_MyStructArray);这个宏展开之后的代码为:

PARAM_PASSED_BY_REF(Z_Param_Out_MyStructArray, UArrayProperty, TArray<FMyStruct>)

最终展开为:

TArray<FMyStruct> Z_Param_Out_MyStructArrayTemp;
TArray<FMyStruct>& Z_Param_Out_MyStructArray = Stack.StepCompiledInRef<UArrayProperty, TArray<FMyStruct> >(&Z_Param_Out_MyStructArrayTemp);

综合上面两个例子,我们发现核心操作都是调用template<class TProperty> void FFrame::StepCompiledIn(void*const Result)这个模板函数。通过跟踪这个函数的执行,发现它实际调用了UObject::execInstanceVariable()函数。

  1. 更新"FFrame::PropertyChainForCompiledIn"这个成员变量;
  2. 使用更新后的“FFrame::PropertyChainForCompiledIn”值,更新了"FFrame::MostRecentPropertyAddress"成员变量。

再结合引擎中CustomThunk函数的实现源码,可以得出这样的结论:

  1. 通过调用Stack.StepCompiledIn()函数,就可以更新蓝图虚拟机的栈顶指针;

  2. Stack.MostRecentPropertyAddressStack.MostRecentProperty这两个变量,就是当前参数值的内存地址和反射信息。

有了具体变量的内存地址和类型的反射信息,就足够做很多事了。下面我们就开始实践。

实践1:接受任意UStruct类型参数

下面我们就看一下文章开头的这张图里面的蓝图节点“Show Struct Fields”是如何接受任意类型UStruct参数的。

先上代码, BlueprintWildcardLibrary.h

USTRUCT(BlueprintInternalUseOnly)
struct FDummyStruct {
	GENERATED_USTRUCT_BODY()

};

UCLASS()
class UNREALCOOKBOOK_API UBlueprintWildcardLibrary : public UBlueprintFunctionLibrary {
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintCallable, CustomThunk, Category = "MyDemo", meta = (CustomStructureParam = "CustomStruct"))
		static void ShowStructFields(const FDummyStruct& CustomStruct);
	static void Generic_ShowStructFields(const void* StructAddr, const UStructProperty* StructProperty);

	DECLARE_FUNCTION(execShowStructFields) {

		Stack.MostRecentPropertyAddress = nullptr;
		Stack.MostRecentProperty = nullptr;

		Stack.StepCompiledIn<UStructProperty>(NULL);
		void* StructAddr = Stack.MostRecentPropertyAddress;
		UStructProperty* StructProperty = Cast<UStructProperty>(Stack.MostRecentProperty);


		P_FINISH;

		P_NATIVE_BEGIN;
		Generic_ShowStructFields(StructAddr, StructProperty);
		P_NATIVE_END;
	}
};

BlueprintWildcardLibrary.cpp

#include "BlueprintWildcardLibrary.h"
#include "Engine/Engine.h"

void UBlueprintWildcardLibrary::Generic_ShowStructFields(const void* StructAddr, const UStructProperty* StructProperty) {
	UScriptStruct* Struct = StructProperty->Struct;
	for (TFieldIterator<UProperty> iter(Struct); iter; ++iter) {

		FScreenMessageString NewMessage;
		NewMessage.CurrentTimeDisplayed = 0.0f;
		NewMessage.Key = INDEX_NONE;
		NewMessage.DisplayColor = FColor::Blue;
		NewMessage.TimeToDisplay = 5;
		NewMessage.ScreenMessage = FString::Printf(TEXT("Property: [%s].[%s]"),
			*(Struct->GetName()),
			*(iter->GetName())
		);
		NewMessage.TextScale = FVector2D::UnitVector;
		GEngine->PriorityScreenMessages.Insert(NewMessage, 0);
	}
}

解释一下这段代码:

  1. 首先声明了一个UFunction:static void ShowStructFields(const FDummyStruct& CustomStruct);,其参数类型是“FDummyStruct”,这只是一个占位符;
  2. 在UFUNCTION宏里面指定“CustomThunk”和“CustomStructureParam”;
  3. 实现一个execShowStructFields函数。这个函数很简单,主要是处理蓝图的Stack,从中取出需要的参数,然后对用C++的实现;
  4. 具体功能实现在:static void Generic_ShowStructFields(const void* StructAddr, const UStructProperty* StructProperty)这个函数中。

实践2:对数组中的Struct的数值型求平均

下面我们再来一下文章开头的这张图里面的“Array Numeric Field Average”蓝图节点是如何通过“CustomThunk”函数来实现的。

参照引擎源代码,我定义了这样一个宏,用来从栈上取出泛型数组参数,并正确的移动栈指针:

#define P_GET_GENERIC_ARRAY(ArrayAddr, ArrayProperty) Stack.MostRecentProperty = nullptr;\
		Stack.StepCompiledIn<UArrayProperty>(NULL);\
		void* ArrayAddr = Stack.MostRecentPropertyAddress;\
		UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Stack.MostRecentProperty);\
		if (!ArrayProperty) {	Stack.bArrayContextFailed = true;	return; }

通过这个宏,可以得到两个局部变量:

  • void* ArrayAddr: 数组的起始内存地址;
  • UArrayProperty* ArrayProperty: 数组的反射信息,ArrayProperty->Inner就是数组成员对应的类型了;

有了这个宏,我们就可以很方便的写出thunk函数了:

DECLARE_FUNCTION(execArray_NumericPropertyAverage) {

		// get TargetArray
		P_GET_GENERIC_ARRAY(ArrayAddr, ArrayProperty);

		// get PropertyName
		P_GET_PROPERTY(UNameProperty, PropertyName);

		P_FINISH;

		P_NATIVE_BEGIN;
		*(float*)RESULT_PARAM = GenericArray_NumericPropertyAverage(ArrayAddr, ArrayProperty, PropertyName);
		P_NATIVE_END;
	}

经过以上的准备,我们就已经可以正确的处理“泛型数组”了。下一步就是对这个数组中指定的数“值类型成员变量”求均值了,这主要依靠Unreal的反射信息,一步步抽丝剥茧,找到数组中的每个变量即可。反射系统的使用不是本文的重点,先看完整代码吧。

BlueprintWildcardLibrary.h

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "BlueprintWildcardLibrary.generated.h"

#define P_GET_GENERIC_ARRAY(ArrayAddr, ArrayProperty) Stack.MostRecentProperty = nullptr;\
		Stack.StepCompiledIn<UArrayProperty>(NULL);\
		void* ArrayAddr = Stack.MostRecentPropertyAddress;\
		UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Stack.MostRecentProperty);\
		if (!ArrayProperty) {	Stack.bArrayContextFailed = true;	return; }

UCLASS()
class UNREALCOOKBOOK_API UBlueprintWildcardLibrary : public UBlueprintFunctionLibrary {
	GENERATED_BODY()

public:

	UFUNCTION(BlueprintPure, CustomThunk, meta = (DisplayName = "Array Numeric Property Average", ArrayParm = "TargetArray", ArrayTypeDependentParams = "TargetArray"), Category = "MyDemo")
		static float Array_NumericPropertyAverage(const TArray<int32>& TargetArray, FName PropertyName);
	static float GenericArray_NumericPropertyAverage(const void* TargetArray, const UArrayProperty* ArrayProperty, FName ArrayPropertyName);

public:
	DECLARE_FUNCTION(execArray_NumericPropertyAverage) {

		// get TargetArray
		P_GET_GENERIC_ARRAY(ArrayAddr, ArrayProperty);

		// get PropertyName
		P_GET_PROPERTY(UNameProperty, PropertyName);

		P_FINISH;

		P_NATIVE_BEGIN;
		*(float*)RESULT_PARAM = GenericArray_NumericPropertyAverage(ArrayAddr, ArrayProperty, PropertyName);
		P_NATIVE_END;
	}
};

BlueprintWildcardLibrary.cpp


#include "BlueprintWildcardLibrary.h"
#include "Engine/Engine.h"

float UBlueprintWildcardLibrary::Array_NumericPropertyAverage(const TArray<int32>& TargetArray, FName PropertyName) {
	// We should never hit these!  They're stubs to avoid NoExport on the class.  Call the Generic* equivalent instead
	check(0);
	return 0.f;
}

float UBlueprintWildcardLibrary::GenericArray_NumericPropertyAverage(const void* TargetArray, const UArrayProperty* ArrayProperty, FName PropertyName) {

	UStructProperty* InnerProperty = Cast<UStructProperty>(ArrayProperty->Inner);
	if (!InnerProperty) {
		UE_LOG(LogTemp, Error, TEXT("Array inner property is NOT a UStruct!"));
		return 0.f;
	}

	UScriptStruct* Struct = InnerProperty->Struct;
	FString PropertyNameStr = PropertyName.ToString();
	UNumericProperty* NumProperty = nullptr;
	for (TFieldIterator<UNumericProperty> iter(Struct); iter; ++iter) {
		if (Struct->PropertyNameToDisplayName(iter->GetFName()) == PropertyNameStr) {
			NumProperty = *iter;
			break;
		}
	}
	if (!NumProperty) {
		UE_LOG(LogTemp, Log, TEXT("Struct property NOT numeric = [%s]"),
			*(PropertyName.ToString())
		);
	}


	FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
	int Count = ArrayHelper.Num();
	float Sum = 0.f;

	if(Count <= 0)
		return 0.f;

	if (NumProperty->IsFloatingPoint())
		for (int i = 0; i < Count; i++) {
			void* ElemPtr = ArrayHelper.GetRawPtr(i);
			const uint8* ValuePtr = NumProperty->ContainerPtrToValuePtr<uint8>(ElemPtr);
			Sum += NumProperty->GetFloatingPointPropertyValue(ValuePtr);

		}
	else if (NumProperty->IsInteger()) {
		for (int i = 0; i < Count; i++) {
			void* ElemPtr = ArrayHelper.GetRawPtr(i);
			const uint8* ValuePtr = NumProperty->ContainerPtrToValuePtr<uint8>(ElemPtr);
			Sum += NumProperty->GetSignedIntPropertyValue(ValuePtr);
		}
	}
	// TODO: else if(enum类型)

	return Sum / Count;
}
2018-11-15 17:21:01 xiaoxiaolooi 阅读数 256
  • 从这里开始虚幻4-第2辑-蓝图 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2145 人正在学习 去看看 杨石兴

unreal GIS 模块

unreal4 源代码模块

自从unreal4 开源以来,osg ogre 渐渐不能用来吃饭,于是本着听人劝吃饱饭的思想,转头unreal门下,继续为混口饭事业,投身革命。可是unreal的c++编程基本没有什么例子可看,只好自己从头做起。

  1. 开发者驱动
  2. 编辑器
  3. 工程目录
  4. 基础框架代码
  5. 第三方库

在这里插入图片描述
这个是文档目录,代码量难以阅读,决定从以下三方向入手,第一次写文字,真心不容易。
1.案例编写使用unreal4 的普通界面搭建自定义三维场景 介绍 editor层的基础代码
2.添加GIS地图属性信息 介绍 驱动层的基础代码
3.程序化植物合成 介绍 与unreal基础平台结合
以后会陆续跟新到这里
推荐大家使用 阿里云 https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=ic4sfgeu

2017-03-24 13:46:41 beginner9 阅读数 1171
  • 从这里开始虚幻4-第2辑-蓝图 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2145 人正在学习 去看看 杨石兴

学习心得,做下笔记,防止忘~

1.新建关卡(level)

2.新建蓝图类


3.打开蓝图类

视口——>rendering


出现龙头,如下图,很好!


4.把“”“视口”右下角的input 中的Auto Receive input 设置为“”“player 0”,如下图:


5.在“”“事件图表”中添加变量


6.绘制蓝图关系

创建成功关系图后一定要编译


7.设置循环1-10次,如下图:


end~运行效果图:

按“”“F”键盘上的按键,就可在看见效果


2016-08-11 17:32:26 sgnyyy 阅读数 738
  • 从这里开始虚幻4-第2辑-蓝图 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2145 人正在学习 去看看 杨石兴

1. 通过设置两个蓝图的变量,并通过CreateUIWidget保存获取相应的UI界面,这样另一个蓝图就可以获取对象,从而通过变量进行沟通

2. 通过命令行Execute Console Command

3. 通过Event Dispatchers进行绑定时间,并在触发的地方Call。 注意:绑定bind的事件已经要运行到节点才能绑定上。例子中使用了在初始化的时候就进行了绑定



没有更多推荐了,返回首页