ue4 find 按时间排序sessions的结果依据什么排序

  Project Tango应该说是Google一试水AR的设备,其中Project Tango主要二个功能,一个是获取深度信息,如MS的Kinect,有相当多的设备都有这个功能,二是第一人称相对定位,这个就没那么常见了,如果对这个设备有更深的兴趣,可以看知乎上的这二个链接。
  在这就不仔细来说这个东东了,上面二个链接比我自己再来说篇好多了,Project Tango本身有Unity3D的包()如果在Unity3D下开发,相应的东东都已经提供,还有一些实例,能够很容易就开发基于Project Tango的功能出来。
  UE4下就比较麻烦了,google没有针对UE4做相应的包,不过,google提供针对安卓开发的项目,一种是Android Studio项目,一个是供JNI调用的C语言项目。
  UE4针对移动平台感觉还是不那么友好,如Unity调用Android项目,大家顺便一搜,都能搞定,而在UE4下引用安卓项目,如下是一个添加针对Android支持Google Play功能的committed。
  可以看到,有些复杂,特别针对我这种UE4与Android都不熟的人,只有想别办法,首先我的需求并不复杂,只是在一个模型与现实重叠的空间利用Project Tango的Motion tracking功能行走,旋转等,简单来说,我现在的办公室环境,利用3D建模做一个和办公室一样的模型,长宽都要对上,这样利用Project Tango 的Tracking,我能只看屏幕也知道我在办公室的那个位置,前面是否有障碍物,就如HTV vive的那二个像个小音箱的东东来检测可活动区域一样。
  UE4本身的脚本就是C++语言,自然我就想到利用上面的tango c来做开发,如下主要记录本文实践这种方法遇到的一些问题。
  首先安装UE4的安卓开发相关所有软件,UE4已经帮你差不多都搞好。&,然后对照这个链接下的UE4安卓快速入门自己测试,一个简单的UE4 安卓程序发出来就没问题了。
  tango-examples-c里很简单,一个so文件,相当于win平台中的动态链接库文件,二是一个头文件tango_client_api.h.现在就很简单了,相当于平常我们写C++程序一样,引入动态链接库,然后添加头文件就可,但是UE4中,编译都是靠对应目录下的cs文件编译,我们需要让UE4的规则来引入库与头文件。
  按照UE4大家默认的一些目录位置与命名,先把相应头文件与库放入一个ThirdParth文件夹,然后放入UE4工程文件件,与Source,Binaries等目录平行,如下:
  然后打开工程名.Build.cs文件,告诉编译器我们需要引入的库与文件。&
public class Office_05 : ModuleRules
public Office_05(TargetInfo Target)
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Launch", "UMG" });
//PrivateDependencyModuleNames.AddRange(new string[] { "" });
//PublicIncludePaths.Add("Runtime/Launch/Public");
//PrivateIncludePaths.Add("Runtime/Launch/Private/Android");
if (Target.Platform == UnrealTargetPlatform.Android)
PublicIncludePaths.AddRange(new string[] { "Core" });
PublicIncludePaths.Add("C:/NVPACK/android-ndk-r10e/platforms/android-19/arch-arm/usr/include");
LoadBobsMagic(Target);
private string ModulePath
get { return Path.GetDirectoryName(RulesCompiler.GetModuleFilename(this.GetType().Name)); }
private string ThirdPartyPath
get { return Path.bine(ModulePath, "../../ThirdParty/")); }
public bool LoadBobsMagic(TargetInfo Target)
bool isLibrarySupported = false;
if (Target.Platform == UnrealTargetPlatform.Android)
isLibrarySupported = true;
string LibrariesPath = bine(ThirdPartyPath, "tango_client_api", "lib");
PublicAdditionalLibraries.bine(LibrariesPath, "libtango_client_api.so"));
if (isLibrarySupported)
// Include path
PublicIncludePaths.bine(ThirdPartyPath, "tango_client_api", "include"));
//Definitions.Add(string.Format("WITH_LIBZPLAY_BINDING={0}", isLibrarySupported ? 1 : 0));
return isLibraryS
UE4 引入库
&  PublicDependencyModuleNames我们新添加Launch与UMG,因为我们需要引用这二个库,不然后面引用#include "Android/AndroidJNI.h"里的功能会告诉你没有实现,还有一点特别注意,安卓的功能一定要包含预处理定义PLATFORM_ANDROID当中,当初特别二的以为编译选项选择Android后,就可以直接写NDK代码了,当然现在的编译是由UE4来控制的,所以不用看VS中的错误列表,只需要看VS中的输出,如果提示生成成功,没有error,就可以在设备上发布了。
  然后我们包装一下tango_client_api.h里的功能,演示如何在安卓环境下调用JNIEnv,当前active等。
#pragma once
#include "Components/TextRenderComponent.h"
#include "Office_05.h"
#include "MyCharacter.h"
#include "TangoApp.h"
#include "Engine/TextRenderActor.h"
#include "Components/TextRenderComponent.h"
#if PLATFORM_ANDROID
#include "tango_client_api.h"
#include "Android/AndroidApplication.h"
#include "Android/AndroidJava.h"
#include "Android/AndroidJNI.h"
class OFFICE_05_API TangoApp
static class UTextRenderComponent* textR
TangoApp();
~TangoApp();
static FVector& GetTranslation()
static FQuat& GetQuat()
static void SetTextRender(class UTextRenderComponent* tRender)
textRender = tR
appendText(textRender, TEXT("VV"));
static void appendText(UTextRenderComponent* textCompent, const FString& value, bool overrid = false)
if (textCompent == nullptr)
if (!overrid)
FText text = textCompent-&T
FString newText = text.BuildSourceString();
newText.Append(" ");
newText.Append(value);
textCompent-&SetText(FText::FromString(newText));
textCompent-&SetText(FText::FromString(value));
#if PLATFORM_ANDROID
static void InitTango()
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
jint VersionJint = Env-&GetVersion();
int8 Version = (int8)VersionJ
TangoApp::appendText(textRender, FString::FromInt(Version));
jobject currentActive = FAndroidApplication::GetGameActivityThis();
TangoErrorType type = TangoService_initialize(Env, currentActive);
TangoApp::appendText(textRender, FString::FromInt((int)type));
//type = TangoService_setBinder(Env, currentActive);
TangoApp::appendText(textRender, TEXT("A"));
TangoApp::appendText(textRender, FString::FromInt((int)type));
auto tangoConfig = TangoService_getConfig(TANGO_CONFIG_DEFAULT);
type = TangoConfig_setBool(tangoConfig, "config_enable_motion_tracking", true);
TangoApp::appendText(textRender, FString::FromInt((int)type));
if (type == TANGO_SUCCESS)
TangoApp::appendText(textRender, TEXT("B"));
type = TangoConfig_setBool(tangoConfig, "config_enable_auto_recovery", true);
TangoApp::appendText(textRender, FString::FromInt((int)type));
if (type == TANGO_SUCCESS)
TangoApp::appendText(textRender, TEXT("C"));
type = TangoConfig_setBool(tangoConfig, "config_enable_learning_mode", true);
TangoApp::appendText(textRender, FString::FromInt((int)type));
if (type == TANGO_SUCCESS)
TangoApp::appendText(textRender, TEXT("D"));
//TangoApp::Launch();
//TangoApp::appendText(text-&GetTextRender(), TangoApplication::getPackageName());
//uuid得不到,相应权限申请不成功,请看TangoApplication::Launch
char* uuidList = NULL;
type = TangoService_getAreaDescriptionUUIDList(&uuidList);
TangoApp::appendText(textRender, FString::FromInt((int)type));
if (type == TANGO_SUCCESS)
int lenght = 0;
for (int i = 0; i & 1000; i++)
if (uuidList[i] != 0)
FString suuidList(uuidList);
TangoApp::appendText(textRender, FString::FromInt(lenght));
TangoCoordinateFrameP
pair.base = TANGO_COORDINATE_FRAME_START_OF_SERVICE;
pair.target = TANGO_COORDINATE_FRAME_DEVICE;
//用来验证相应数据
//TangoPoseData* poseData = new TangoPoseData();
//pair.base = TANGO_COORDINATE_FRAME_IMU;
//pair.target = TANGO_COORDINATE_FRAME_CAMERA_COLOR;
//TangoService_getPoseAtTime(0, pair, poseData);
//type = TangoService_connectOnTangoEvent(&TangoApp::onTangoConnectEvent);
//TangoApp::appendText(textRender), TEXT("E"));
//TangoApp::appendText(textRender, FString::FromInt((int)type));
type = TangoService_connectOnPoseAvailable(1, &pair, &TangoApp::TangoService_onPoseAvailable);
TangoApp::appendText(textRender, TEXT("F"));
TangoApp::appendText(textRender, FString::FromInt((int)type));
//FAppEventManager::GetInstance()-&PauseRendering();
type = TangoService_connect(nullptr, tangoConfig);
TangoApp::appendText(textRender, FString::FromInt((int)type));
//FAppEventManager::GetInstance()-&ResumeRendering();
static FString getPackageName()
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv(true))
auto getPackageMethod = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "getPackageName", "()Ljava/lang/S", false);
jstring jsString = (jstring)FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis, getPackageMethod);
check(jsString);
const char * nativeName = Env-&GetStringUTFChars(jsString, 0);
FString ResultName = FString(nativeName);
Env-&ReleaseStringUTFChars(jsString, nativeName);
Env-&DeleteLocalRef(jsString);
return ResultN
return FString();
static void Launch()
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv(true))
//申请ADF权限
auto intentClass = Env-&FindClass("android/content/Intent");
auto Constructor = Env-&GetMethodID(intentClass, "&init&", "()V");
auto intentObject = Env-&NewObject(intentClass, Constructor);
auto intentMethod = FJavaWrapper::FindMethod(Env, intentClass, "setClassName", "(Ljava/lang/SLjava/lang/S)V", false);
//auto putExtraMethod = FJavaWrapper::FindMethod(Env, intentClass, "putExtra", "(Ljava/lang/SLjava/lang/S)V", false);
FJavaWrapper::CallVoidMethod(Env, intentObject, intentMethod, "com.projecttango.tango", "com.google.atap.tango.RequestPermissionActivity");
FJavaWrapper::CallVoidMethod(Env, intentObject, putExtraMethod, "PERMISSIONTYPE", "ADF_LOAD_SAVE_PERMISSION");
auto startAMehtod = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "startActivity", "(android/content/I)V", false);
//FJavaWrapper::CallVoidMethod(Env, currentActive, startAMehtod, intentObject);
//check(object);
//auto intentObject = Env-&NewGlobalRef(object);
Env-&DeleteLocalRef(intentObject);
//auto intentMethod = FJavaWrapper::FindMethod(Env, intentClass, "setClassName", "(Ljava/lang/SLjava/lang/S)V", false);
//auto putExtraMethod = FJavaWrapper::FindMethod(Env, intentClass, "putExtra", "(Ljava/lang/SLjava/lang/S)V", false);
FJavaWrapper::CallVoidMethod(Env, intentObject, intentMethod, "com.projecttango.tango", "com.google.atap.tango.RequestPermissionActivity");
FJavaWrapper::CallVoidMethod(Env, intentObject, putExtraMethod, "PERMISSIONTYPE", "ADF_LOAD_SAVE_PERMISSION");
auto startAMehtod = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "startActivity", "(android/content/I)V", false);
//FJavaWrapper::CallVoidMethod(Env, currentActive, startAMehtod, intentObject);
//FJavaClassObject intentObject(FName("android/content/Intent"), "()V");
//auto intentMethod = intentObject.GetClassMethod("setClassName", "(Ljava/lang/SLjava/lang/S)V");
//auto putExtraMethod = intentObject.GetClassMethod("putExtra", "(Ljava/lang/SLjava/lang/S)V");
//intentObject.CallMethod(intentMethod, "com.projecttango.tango", "com.google.atap.tango.RequestPermissionActivity");
//intentObject.CallMethod(intentMethod, "PERMISSIONTYPE", "ADF_LOAD_SAVE_PERMISSION");
//auto startAMehtod = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "startActivity", "(android/content/I)V", false);
//FJavaWrapper::CallVoidMethod(Env, currentActive, startAMehtod, intentObject.GetJObject());
//auto tangoClass = Env-&GetObjectClass(currentActive);
//auto tangoMethod = FJavaWrapper::FindMethod(FAndroidApplication::GetJavaEnv(true), FJavaWrapper::GameActivityClassID, "launchIntent", "(Ljava/lang/SLjava/lang/S[Ljava/lang/SI)V", false);
//auto tangoMethod = FJavaWrapper::FindMethod(Env, tangoClass, "launchIntent", "(Ljava/lang/SLjava/lang/S[Ljava/lang/SI)V", false);
//if (tangoMethod != nullptr)
TangoApplication::appendText(text-&GetTextRender(), TEXT("C"));
//使用方法请看AndroidJNI.cpp -&AndroidThunkCpp_Iap_QueryInAppPurchases
jobjectArray tArgs = (jobjectArray)Env-&NewObjectArray(1, FJavaWrapper::JavaStringClass, NULL);
jstring StringValue = Env-&NewStringUTF(TCHAR_TO_UTF8("PERMISSIONTYPE:ADF_LOAD_SAVE_PERMISSION"));
Env-&SetObjectArrayElement(tArgs, 0, StringValue);
Env-&DeleteLocalRef(StringValue);
FJavaWrapper::CallVoidMethod(Env, currentActive, tangoMethod, "com.projecttango.tango",
"com.google.atap.tango.RequestPermissionActivity", tArgs, 43);
static void onTangoConnectEvent(void* context, const TangoEvent* event)
appendText(textRender, TEXT("Connect"), true);
appendText(textRender, FString::SanitizeFloat(event-&timestamp));
//Unity x左右,y上下,z前后 左手
///project-tango/overview/coordinate-systems#project_tango_coordinate_frames
//Tango START_OF_SERVICE x左右,y前后,z上下 右手
//Tango Device_Frame x左右,y上下,z前后 右手
//UE4 x前后,y左右,z上下 左手
static void TangoService_onPoseAvailable(void* context, const TangoPoseData* pose)
if (pose-&status_code == TANGO_POSE_VALID &&
pose-&frame.base == TANGO_COORDINATE_FRAME_START_OF_SERVICE &&
pose-&frame.target == TANGO_COORDINATE_FRAME_DEVICE
translation[0] = pose-&translation[0] * 100;
translation[1] = pose-&translation[1] * 100;
translation[2] = pose-&translation[2] * 100;
quat.X = pose-&orientation[0];
quat.Y = pose-&orientation[1];
quat.Z = pose-&orientation[2];
quat.W = pose-&orientation[3];
#include "Office_05.h"
#include "TangoApp.h"
class UTextRenderComponent* TangoApp::textRender =
FVector TangoApp::translation = FVector::ZeroV
FQuat TangoApp::quat = FQuat::I
TangoApp::TangoApp()
TangoApp::~TangoApp()
  在这里有个失败的尝试,我想通过NDK来申请ADF(区域文件相关权限),见上面的Launch方法,总是在调用CallVoidMethod时失败,而上面的getPackageName又没有问题,想不出来是啥问题。
  通过工具Android Device Monitor,一般来说如果按照UE4的安卓工具包的流程来安装,这个工具在目录C:\NVPACK\android-sdk-windows\tools\monitor.bat下,打开Android Device Monitor,一般来说,我们新建一个Filters,如下图设置。
  如上图设置后,我们得到错误信息是FindMethod得到的方法为空,到这一步后,相关参数应该没有问题,可能是还要引入新的库,总之,在这我们得不到区域文件,那么我们不能通过区域文件来定义,只能通过设备开始位置来定位了,这样我们需要在特定的位置,特定的方向打开这个程序才能正确tracking现实与模型,这样限制太大,所以我们需要提供一UI可以自己修改位置与方向,这样,在开始时,我们先调到我们本身的位置与方向与项目的位置与方向重合。还好,这个东东并不需要我们多花费时间,UE4里本身的功能与内容包里,就有一个C++功能First Person,我们添加到项目中,这个在安卓下就提供二个圈给我们,一个圈调整水平位置,一个调整视角方向,刚好满足我们的需求,现在我们结合First Person与Tango,让Tango本身的路径追踪来替代First Person里的行走,如下是主要的修改位置。
void AFP_FirstPersonCharacter::BeginPlay()
Super::BeginPlay();
text = FindActor&ATextRenderActor&(TEXT("TextRenderActor2"));
text1 = FindActor&ATextRenderActor&(TEXT("TextRenderActor3"));
TangoApp::SetTextRender(text-&GetTextRender());
#if PLATFORM_ANDROID
TangoApp::InitTango();
//TangoApp::Launch();
TangoApp::appendText(text-&GetTextRender(), TEXT("T"), true);
TangoApp::appendText(text1-&GetTextRender(), TEXT("R"), true);
void AFP_FirstPersonCharacter::Tick(float DeltaTime)
Super::Tick(DeltaTime);
#if PLATFORM_ANDROID
//Tango Device_Frame/OpenGL
x左右,y上下,z前后 右手
x前后,y左右,z上下 左手
//Tango START_OF_SERVICE
x左右,y前后,z上下 右手
x左右,y上下,z前后 左手
////Device_Frame (Unreal camera to Drive)
//FMatrix ucTd(-FVector::UpVector, FVector::ForwardVector, FVector::RightVector, FVector::ZeroVector);
////(Drive to START_SERVICE)
//FTransform dTss(TangoApp::GetQuat(), TangoApp::GetTranslation(), FVector(1, 1, 1));
////X,Y互换 (START_SERVICE to Unreal world)
//FMatrix ssTuw(FVector::RightVector, -FVector::ForwardVector, FVector::UpVector, FVector::ZeroVector);
////ucTd * dTss * ssTuw
//FTransform dTuw = FTransform(ucTd) * dTss * FTransform(ssTuw);
//SetActorRelativeLocation(dTuw.GetLocation());
//auto rotator = dTuw.Rotator();// (dTuw.GetRotation() * FQuat::FQuat(FVector::ForwardVector, -PI / 2.0f)).Rotator(); //dTuw.Rotator();//
auto ToConvert = TangoApp::GetQuat();
auto translation = TangoApp::GetTranslation();
FQuat TangoToUnrealQuat(ToConvert.Z, -ToConvert.X, -ToConvert.Y, ToConvert.W);
//0.7071(rad/2) angle = 90 axis = (0,1,0)
FQuat ConvertedQuat = (FQuat(0.0, 0.7071, 0.0, 0.7071) * TangoToUnrealQuat);
if (WeaponRange & 0 || WeaponRange &100)
WeaponRange = 0;
FVector ConvertedFVector = 1 * cRotaror.RotateVector(FVector(translation[1], translation[0], translation[2] + WeaponRange)) + cP
SetActorRelativeLocation(ConvertedFVector);
FRotator rotator = ConvertedQuat.Rotator() + cR
if (Controller != nullptr)
Controller-&SetControlRotation(rotator);
//if (GEngine)
auto quat = ConvertedQ //dTuw.GetRotation();//
GEngine-&AddOnScreenDebugMessage(0, 30.f, FColor::Red, "X:" + FString::SanitizeFloat(quat.X)
+ " Y:" + FString::SanitizeFloat(quat.Y)
+ " Z:" + FString::SanitizeFloat(quat.Z));
if (GEngine)
GEngine-&AddOnScreenDebugMessage(0, 30.f, FColor::Red, "H:" + FString::SanitizeFloat(WeaponRange));
void AFP_FirstPersonCharacter::MoveForward(float Value)
if (Value != 0.0f)
// Add movement in that direction
AddMovementInput(GetActorForwardVector(), Value);
cPostion += GetActorForwardVector()*V
void AFP_FirstPersonCharacter::MoveRight(float Value)
if (Value != 0.0f)
// Add movement in that direction
AddMovementInput(GetActorRightVector(), Value);
cPostion += GetActorRightVector()*V
void AFP_FirstPersonCharacter::TurnAtRate(float Rate)
// Calculate delta for this frame from the rate information
AddControllerYawInput(Rate * BaseTurnRate * GetWorld()-&GetDeltaSeconds());
cRotaror.Yaw += Rate * BaseTurnRate * GetWorld()-&GetDeltaSeconds();
void AFP_FirstPersonCharacter::LookUpAtRate(float Rate)
// Calculate delta for this frame from the rate information
AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()-&GetDeltaSeconds());
cRotaror.Pitch += Rate * BaseLookUpRate * GetWorld()-&GetDeltaSeconds();
Tango Tracking
  Tango里需要注意,每个摄像机,还有设备本身都采用不同的坐标系,如下图。
  想看更完全的介绍,请看 & 这个链接,如果不能打开,提示二个字,红杏。
  嗯,在这差不多了,一个简单利用Tango tracking,漫游办公室的小程序就有了,是不是有点AR的感觉。Tango这部分就差不多完了,但是有个小问题,UI能调整水平方向,高度不能调整,我们增加一个调整高度的UI,顺便演练下蓝图调用C++的API的方法。
  首先我们创建一个基于GameMode的我们自己的MyGameMode.
  然后和上面一样,创建一个基于HUD的自己的MyHUD,在MyGameMode中的HUD选择MyHUD,在蓝图中,我们选择添加用户控件里的用户蓝图,名字设为MyGUI.
  在MyHUD中,添加MyGUI到视图中,相应蓝图设置如下。
&  在MyGUI中添加一个Slider,其中OnValueChanged中设置如下:
  其中FP_FirstPersonCharacter添加如下方法。
UFUNCTION(BlueprintCallable, Category = "Game")
void SetHeight(float height);
  特性声明BlueprintCallable,其中Category是在蓝图中添加方法的分组名。
  差不多完了,最后想起一个问题,在从&里的一个API,FindObject&T&,使用不成功,在AActor里使用反正得不到结果,同样的参数,传入如下函数就可以。
template& class T &
T* FindActor(FString name)
for (TActorIterator&T& It(GetWorld()); It; ++It)
T* actor = *It;
if (It-&GetName() == name)
  有知道的同学可以说下。原文链接:
阅读排行榜C++基础(28)
游戏开发(13)
1.1 什么是UE4反射
在UE4里面,你无时无刻都会看到类似UFUNCTION()这样的宏。官方文档告诉你,只要在一个函数的前面加上这个宏,然后在括号里面加上BlueprintCallable就可以在编辑器里面调用了。按照他的指示,我们就能让我们的函数实现各种各样特别的功能,那这个效果就是通过UE4的反射系统来实现的。这看起来确实非常棒,不过同时给UE4的反射系统增添了一点神秘感。我们可能一开始尝试着去找一下这个宏的定义,但是翻了几层发现没有头绪,可能也就懒得再去研究他是怎么实现的了。
其实,所谓反射,是程序在运行时进行自检的一种能力,自检什么呢?我认为就是检查自己的C++类,函数,成员变量,结构体等等(对应起来也就是大家在UE4能看到的UCLASS,UFUNCTON,UPROPERTY,USTRUCT后面还会提到)。
那检查这些东西做什么呢?最明显的就是支持蓝图和C++的交互功能,说的更通俗一点,就是可以更自由的控制这些结构,让他在我们想出现的地方出现,让他在我们想使用的地方使用。要知道我们在虚幻4中声明的任意一个类,都是继承于UObject类的,所以他远远不是我们所以为的那个普通的C++类。我们可以使用这个类进行网络复制,执行垃圾回收,让他和蓝图交互等等。而这一切原生的C++是并不支持的,也正是因此虚幻4才构建了一个这样的反射系统。
1.2反射一般用于哪些地方
在UE4里面, 基本上所有的游戏工程的类都需要用到。比如,你用编辑器新建一个类,类的前面会自动添加UCLASS();新建一个结构体,需要使用USTRUCT();新建一个枚举变量,需要在前面声明UENUM();在类的里面,也必须要加上GENERATED_UCLASS_BODY()才行。
如果你想让你的变量能显示在编辑器里面,想让你的函数可以被蓝图调用或者通过让这个函数实现RPC网络通信功能,或者你想让你的变量被系统自动的回收,这些都离不开反射系统以及这些宏定义。
所以,我们这里起码能认识到,在网络通信,蓝图交互以及垃圾回收方面,这与反射系统是密不可分的。
另外,如果要说引擎中哪部分使用到反射系统功能的话,那基本上整个引擎都脱不了干系了。
在了解反射系统前,我们必须要知道两个UE4独特的文件类型—“.generate.h”以及“.generate.cpp”。“.generate.h”文件在每一个类的声明前面都会被包含到对应的头文件里面。(这也是官方建议我们要用编辑器来创建类的原因,他们并不是常规的C++类)而“.generate.cpp”对于一整个项目只会有一个。这两种文件可以说是反射系统的关键所在,他们是通过Unreal Build Tool(UBT) 和UnrealHeaderTool(UHT)来生成的。
(补充:UnrealBuildTool(简称 UBT)是一个自定义工具,管理多个编译配置中的&虚幻引擎 4(UE4)源代码编译过程。
UnrealHeaderTool(简称 UHT)是支持 UObject 系统的自定义解析和代码生成工具。代码编译在两个阶段中进行:1.HT 被调用。它将解析 C++ 头中引擎相关类元数据,并生成自定义代码,以实现诸多 UObject 相关的功能。2.普通 C++ 编译器被调用,以便对结果进行编译。)
“.generate.h”与“.generate.cpp”文件都是做什么的?“.generate.h”与“.generate.cpp”文件里面都是什么?“.generate.h”里面是宏,而且包含一个非常庞大的宏,这个宏把所有和反射相关的方法(包括定义)和结构体连接到一起。而“.generate.cpp”里面是许多的函数定义,UnrealHeaderTool根据你在头文件里面使用的宏(UFUNCTION等)自动的生成这个文件,所以这个文件并不需要你去修改,也不允许修改。UBT属性通过扫描头文件,记录任何至少有一个反射类型的头文件的模块。如果其中任意一个头文件从上一次编译起发生了变化,那么
UHT就会被调用来利用和更新反射数据。UHT分析头文件,创建一系列反射数据,并且生成包含反射数据的C++代码(也就是“.generate.cpp”)以及各种辅助函数与thunk函数(“.generate.h”)。
这里简单举个例子,假如你有个类方法前面用宏定义(UFUNCTION(BlueprintNativeEvent))声明(BlueprintNativeEvent在ObjectBase.h中有定义,表示蓝图本地要实现的事件),你的cpp文件里面就不能去写这个函数的定义而是_
Implementation,因为UE4会认为你这个方法只想在蓝图实现,所以这个函数会在生成工具生成的过程中被定义在“.generate.cpp”里面,如果你在cpp定义就会提示编译错误(当然,因为你定义了两个一样的函数)。当你去调用这个函数的时候,首先会执行“.generate.cpp”里面的定义,之后会通过反射系统调用到蓝图。如果蓝图没有定义,那么最后会调用到GENERATED_UCLASS_BODY(后面会介绍这个宏),进而定位到.generate.h文件里面,最后执行“函数名_Implementation”,这也就是为什么要求我们在必须在.cpp声明为_
Implementation的原因。(该现象在4.9之前是经过验证的,不过最新版本的在写法上可能略有区别,等博主了解完新版本的区别后再做修改)
3.反射系统的类型层次:
按顺序依次是UField ,UStruct,UClass,UScriptStruct,UFunction,UEnum,Uproperty
UStruct是所有聚合结构体的基础类型(包含其它成员的类型,比如一个C++类、结构体、或者函数),不应该跟C++中的结构体(struct)混为一谈(那是UScriptStruct)。UClass可以包含函数、属性以及它们的子类,而UFunction和UStriptStruct只能包含属性。
你可以通过使用UTypeName::StaticClass()或者FTypeName::StaticStruct()来获取反射类型对应的UClass以及UScriptStruct,你也通过 一个UObject的实例通过Instance-&GetClass()来获取类型(不能通过一个结构体实例的获取类型,因为结构体没有一个通用的基类或者需要的存储空间)。
前面简单描述了一个UFUNCTION(BlueprintNativeEvent)的例子。这里我们以游戏中玩家使用道具的流程UFUNCTION(,Server)
ServerRequestUseItem作为例子来解析一下反射的执行过程。首先需要知道的是,宏的UFUNCTION(,Server)意义。这是RPC通信,表示这个方法在客户端调用,服务器执行。而且我们在对应.cpp文件的函数实现必须要以“函数名_Implementation”来命名。&&&
& & & &“工程名.generated.cpp”的文件一般位于“Project\工程名\Intermediate\Build\Win64\ Inc\工程名\工程名.generate.cpp”。
& & & &“工程名.generated.h”的文件一般位于“Project\工程名\Intermediate\Build\Win64\ Inc\工程名\类名.generate.h”。
UE4Editor—ProjectName.dll! APlayerController::ServerRequestUseItem_Implementation(parameter1)
UE4Editor—ProjectName.dll!APlayerController::execServerRequestUseItem_Implementation(FFrame&Stack,void*const Result)
UE4Editor—CoreUObject.dll! UFunction::Invoke(Object*Obj,FFrame&Stack,void*const Result)
UE4Editor—CoreUObject.dll! UObject::ProcessEvent(UFunction*Function,void* Parms)
UE4Editor—Engine.dll! AActor::ProcessEvent(UFunction*Function,void* Parameters)
UE4Editor—ProjectName.dll! APlayerController::ServerRequestUseItem (parameter1)
UE4Editor—ProjectName.dll! APlayer::ClickItemUI ()
请参考上面这个代码执行栈来阅读下面的内容。UHT会给工程自动生成“工程名.generated.cpp ”的文件,里面包含了所有类的包含前置标记的的类方法,如网络通信UFUNCTION(,Server)以及C++和蓝图交互UFUNCTION(BlueprintNativeEvent)。因为我们在该类对应的.cpp文件只能声明_imlementation的函数,所以引擎在调用有这种标记的函数的时候,会执行以下的步骤。
& & & & &①会去.generated.cpp去找函数的具体实现
&&&&&&&& ②通过引擎工具自动生成的函数会调用位于“.generate.cpp”文件中的方法定义并执行
ProcessEvent(FindFunctionChecked(SHOOTERGAME_ServerRequestUseItem),&Parms);接下来之所以会调用void::ProcessEvent(UFunction*,void*)。是因为一般的charactercontroller和pawn等实际存在于游戏中的实体都是继承自Actor的,所以他可以直接调用父类的ProcessEvent方法。
& & & & ③由于AActor调用Super::ProcessEvent(,);所以会执行UObject的ProcessEvent,该函数的具体实现在ScriptCore.cpp(classCOREUOBJECT_APIUObject
: publicUObjectBaseUtility,Uobject的具体实现分布在几个不同的文件里面,如obj.cpp、CoreUObjectPrivate.cpp等)。
& & & & ④Uboject会调用-&Invoke(this,,(uint8*)
+-&);NewStack是根据前面的Funtion参数创建的一个执行堆。
& & & &⑤在每个类的声明前我们都能看到UCLASS,这是UE4规定的格式。其实我们可以在看到对应类的.generated.cpp 文件中看到如下的宏定义。最后其实都是一些类与结构体的定义,
#undef UCLASS
#undef UINTERFACE
#if UE_BUILD_DOCS
#define UCLASS(…)
#define UCLASS(…) \
APlayerController_EVENTPARMS
#define APlayerController_EVENTPARMS\
struct PlayerController_eventCheckCheatPassword_Parms\
&&& FString P
在类的定义的前面我们还会看到GENERATED_UCLASS_BODY()。这里的宏我们进一步展开,
#undef GENERATED_UCLASS_BODY
#undef GENERATED_INTERFACE_BODY
#define GENERATED_UCLASS_BODY()
&&&& APlayerController_RPC_WRAPPERS\
APlayerController_CALLBACK_WRAPPERS\
APlayerController_INCLASS \
DECLARE_FUCTION(execServerRequestUseItem)\
P_GET_OBJECT(UComponent,itemComp); \
P_GET_STRUCT(struct FItemID,itemID);
P_FINISH; \
this-&ServerRequestUseItem_Implmentation(itemComp);
我们最后发现这个execServerRequestInventoryUseItem其实就是在GENERATED_UCLASS_BODY()定义的一个函数,在这里我们找到了ServerRequestUseItem_Implementation找到了也就是在.cpp文件服务器最终执行的函数。
在上面流程的第三步和第四步,涉及到了ProcessEvent和Invoke两个函数。这里面的机制比较复杂,整体来说是通过新建的结构体和代理来保存和传递函数指针。ProcessEvent会传递函数的名称以及参数,函数的地址与名称通过类UFuntion来保存,参数这回通过定义一个新的类来保存。然后将函数的名称与参数类的地址传递。要注意的是,在执行ProcessEvent(FindFuntionChecked(,
&)的时候,FindFuntionChecked执行后所返回的UFuntion里包含一个成员函数指针,这个指针指向的就是execServerRequestUseItem。这些内容都是在UE4的编译工具编译时所产生的。
这个文档只是简单的从一个反射方法的实现流程上做了分析,而UE4的反射系统还是非常复杂和强大的。这里面涉及到宏的使用,蓝图系统,编译工具,成员函数指针以及一些特别的存储类,想完全理解这些可能需要非常长的时间,也确实比较难。我们平时写逻辑时也许根部不需要了解,但是对于一个程序员来说,理解总比不懂强,也许会给我们以后的设计增加一些灵感,有助于我们进一步理解UE4,以后如果有更深的理解会进一步完善这个博客的。
参考文档链接:/blog/unreal-property-system-reflection
参考文档翻译链接:/ghl_carmack/p/5698438.html#
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:19858次
排名:千里之外
原创:47篇
评论:11条
(2)(1)(2)(3)(12)(6)(6)(3)(3)(6)(7)

我要回帖

更多关于 linux find 时间排序 的文章

 

随机推荐