U3D转UE4快速编程指南

1

主题

1

帖子

3

积分

新手上路

Rank: 1

积分
3
发表于 2022-11-28 15:23:00 | 显示全部楼层
整理了一些从U3D转到UE4需要了解的基础知识点,根据个人情况可以跳过部分。(持续优化中)
一、基础知识相关文档链接:

二、基础概念补充

1、常用命名约定

Uxxx:继承自UObject
Axxx:继承自AActor
Fxxx:一般都是struct等不能被继承的结构体或类,Final的意思
Txxx:模板类或函数
插播:关于UE的命名理念,大概Tim Sweeney最初创建UE时就带入了一点浪漫的人文色彩,就如“Hello World”的故事一样,也只有在对计算机编程产生了一种特殊的情感,才会觉得这么一句简单的话包涵了很深厚的情感寄托。
Actor,可以理解为“演员”,而后继的Pawn(卒)、Character(角色)也很有人文的色彩意义,而Controller,则是Actor、Pawn、Character这些“肉身”的灵魂所在。因此在AAIController类中会发现有UBrainComponent* BrainComponent定义的存在。
而我们所处的现实空间中,也是先有World,才能有Actor,因此Actor都是经过World的Spawn创建出来的。使用Spawn(卵生)则是较为形象并且简洁(动词)的描述创建一个演员对象。
2、GameObject、MonoBehaviour VS AActor、UActorComponent

       UE4的AActor就是U3D的GameObject,官方文档有一点不免严谨的就是在于把AActor类比为了MonoBehaviour,实际上应该是把UActorComponent类比为MonoBehaviour。因此要实现组件类,应该继承自UActorComponent。AActor只是Component的载体。
       UObject相当于UnityEngine.Object。
       由于历史及RPC框架需求的原因,UE4并没有完全用组件化来实现整个应用框架层,因此会有不少基于Actor继承下来的类层次结构,并在其上挂载了相应的组件进行协作。因此可以经常看到类似有AStaticMeshActor的同时也会有UStaticMeshComponent。
       好处在于将预设的GameFrameworks框架业务逻辑规则放于Actor继承体系层,而无关业务逻辑的,放到了UActorComponent的继承体系层。(参考后面GameFrameworks部分)
3、路径命名约定

4、对象生命周期

关于Destroy的C++版本和蓝图版本源代码如下,注意暴露给蓝图的函数都会前辍加个K2,通过指定DisplayName ="DestroyActor"在蓝图节点上显示不带K2的函数名:
bool AActor::Destroy( bool bNetForce, bool bShouldModifyLevel )
{
        // It's already pending kill or in DestroyActor(), no need to beat the corpse
        if (!IsPendingKillPending())
        {
                UWorld* World = GetWorld();
                if (World)
                {
                        World->DestroyActor( this, bNetForce, bShouldModifyLevel );
                }
                else
                {
                        UE_LOG(LogSpawn, Warning, TEXT("Destroying %s, which doesn't have a valid world pointer"), *GetPathName());
                }
        }

        return IsPendingKillPending();
}

/** Destroy the actor */
UFUNCTION(BlueprintCallable, Category="Utilities", meta=(Keywords = "Delete", DisplayName = "DestroyActor", ScriptName = "DestroyActor"))
virtual void K2_DestroyActor();

void AActor::K2_DestroyActor()
{
        Destroy();
}

三、资源路径与对象实例化

1、资源引用路径

       UE的引用资源路径格式为:AssetType'Path/PackageName.AssetName',在Content Browser中找到一个资源进行右键菜单并Copy Reference会出来一串字符为:


StaticMesh'/Game/Geometry/Meshes/1M_Cube.1M_Cube'每段的意义为:


目前UE4采用每个资源一个包,以前UE3和UE2时代,一个包中可以有多个资源对象,因此现在包名和资源名都是一样的,除非有手动重命名过。
注意:在实际编码过程中是不需要前面的StaticMesh类型标识的,这个是在UE3的脚本中需要进行标识时的遗产写法。仔细查看 LoadObject的调用函数会发现在StaticLoadObject中有调用到ResolveName函数,并在ResolveName函数中会一开始就调用ConstructorHelpers::StripObjectClass将类型标识去掉。
注意观察鼠标悬停资源时,出来的Tips中也有Paths,但并不是以/Content/开头:


       这里有一个历史潜规则,游戏工程Content目录下的资源都会以/Game/开头,而引擎Content目录下的资源都会以/Engine/开头标识区别。
创建一个标准的Cone对象并查看其资源路径为:/Engine/BasicShapes/Cone



以下列举一些资源路径示例:
Material'/Game/Geometry/Meshes/CubeMaterial.CubeMaterial'
World'/Game/FirstPersonCPP/Maps/FirstPersonExampleMap.FirstPersonExampleMap'
Blueprint'/Game/FirstPersonCPP/Blueprints/FirstPersonCharacter.FirstPersonCharacter'
Texture2D'/Game/FirstPerson/Textures/FirstPersonCrosshair.FirstPersonCrosshair'
SoundWave'/Game/FirstPerson/Audio/FirstPersonTemplateWeaponFire02.FirstPersonTemplateWeaponFire02'
AnimBlueprint'/Game/FirstPerson/Animations/FirstPerson_AnimBP.FirstPerson_AnimBP'
AnimSequence'/Game/FirstPerson/Animations/FirstPerson_Fire.FirstPerson_Fire'
AnimMontage'/Game/FirstPerson/Animations/FirstPersonFire_Montage.FirstPersonFire_Montage'

StaticMesh'/Engine/BasicShapes/Cone.Cone'
Material'/Engine/BufferVisualization/AmbientOcclusion.AmbientOcclusion'
Blueprint'/Engine/EditorBlueprintResources/ActorMacros.ActorMacros'
SoundCue'/Engine/EditorSounds/GamePreview/EjectFromPlayer_Cue.EjectFromPlayer_Cue'
Texture2D'/Engine/EngineMaterials/Grid.Grid'
2、关于如何创建对象?

U3D中可用用GameObject.Instantiate<>、Resources.Load<>等函数,在UE中对于继承自UObject的类用以下函数实例化:
T* SomeObject = NewObject<T>();
具体函数为:
template< class T >
T* NewObject(UObject* Outer = (UObject*)GetTransientPackage())
如果是继承自 AActor的类,需要通过SpawnActor函数实例化:
GetWorld()->SpawnActor<ATestBPProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, ActorSpawnParams);
SpawnActor有若干个变种函数及模板函数,适用不同的情况,具体请参考World.h中声明。其中GetWorld函数实现如下:
UWorld* AActor::GetWorld() const
{
        // CDO objects do not belong to a world
        // If the actors outer is destroyed or unreachable we are shutting down and the world should be nullptr
        if (!HasAnyFlags(RF_ClassDefaultObject) && ensureMsgf(GetOuter(), TEXT("Actor: %s has a null OuterPrivate in AActor::GetWorld()"), *GetFullName())
                && !GetOuter()->HasAnyFlags(RF_BeginDestroyed) && !GetOuter()->IsUnreachable())
        {
                if (ULevel* Level = GetLevel())
                {
                        return Level->OwningWorld;
                }
        }
        return nullptr;
}
蓝图中可以调用Spawn Actor from Class节点 :


实际调用到的函数为:
class AActor* UGameplayStatics::BeginDeferredActorSpawnFromClass(const UObject* WorldContextObject, TSubclassOf<AActor> ActorClass, const FTransform& SpawnTransform, ESpawnActorCollisionHandlingMethod CollisionHandlingOverride /*= ESpawnActorCollisionHandlingMethod::Undefined*/, AActor* Owner /*= nullptr*/)
UGameplayStatics封装了很多蓝图的工具函数,可以多参考下。
相关文档:

扩展:如果不在AActor的作用域内想SpawnActor可以考虑通过调用 :
GWorld->SpawnActor...
其中GWorld是一个全局变量,UE引擎使用全局变量的用法历史传承,习惯就好。比如全局变量还有如GEngine、GUObjectArray、GUObjectClusters等:
/** Global UWorld pointer. Use of this pointer should be avoided whenever possible. */
extern ENGINE_API class UWorldProxy GWorld;

/** Global engine pointer. Can be 0 so don't use without checking. */
extern ENGINE_API class UEngine*                        GEngine;

/** Global UObject allocator                                                        */
extern COREUOBJECT_API FUObjectArray GUObjectArray;
extern COREUOBJECT_API FUObjectClusterContainer GUObjectClusters;
扩展蓝图函数库:
3、如何实例化蓝图对象

SpawnActor需要传入UClass信息,如果是蓝图对象,要取得其类型,需要在资源路径后加“_C",代表class的类型数据。因此:
Blueprint'/Engine/Tutorial/SubEditors/TutorialAssets/Character/TutorialCharacter.TutorialCharacter'
的UClass类型数据路径为:
Blueprint'/Engine/Tutorial/SubEditors/TutorialAssets/Character/TutorialCharacter.TutorialCharacter_C'
因此实例化蓝图的方法有:
void ATestBPProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
        ……
        DEFINE_LOG_CATEGORY_STATIC(LogBPDynamicLoad, Warning, All);

        // 方法1:通过蓝图对象的GeneratedClass取得类型描述
        UBlueprint* BlueprintObj = LoadObject<UBlueprint>(nullptr, TEXT("Blueprint'/Engine/Tutorial/SubEditors/TutorialAssets/Character/TutorialCharacter.TutorialCharacter'"));
        UE_LOG(LogBPDynamicLoad, Warning, TEXT("Blueprint GeneratedClass:%s"), *BlueprintObj->GeneratedClass->GetName());
        GetWorld()->SpawnActor(BlueprintObj->GeneratedClass, &GetTransform());

        // 方法2:加载蓝图对象的UClass,注意资源路径后要加_C,不然加载出来为null
        UClass* BlueprintClass = LoadClass<AActor>(nullptr, TEXT("Blueprint'/Engine/Tutorial/SubEditors/TutorialAssets/Character/TutorialCharacter.TutorialCharacter_C'"));
        UE_LOG(LogBPDynamicLoad, Warning, TEXT("Blueprint Class:%s"), *BlueprintClass->GetName());
        GetWorld()->SpawnActor(BlueprintClass, &GetTransform());
}
可以看到每次碰撞会创建一个TutorialCharacter出来:


相关文档:
扩展:下面这段代码可以在运行时将一个蓝图资源所在的包内的所有对象都输出查看:
DEFINE_LOG_CATEGORY_STATIC(LogBPPkg, Warning, All);

UBlueprint* BlueprintObj = LoadObject<UBlueprint>(nullptr, TEXT("Blueprint'/Engine/Tutorial/SubEditors/TutorialAssets/Character/TutorialCharacter.TutorialCharacter'"));
if (BlueprintObj)
{
        UPackage* Pkg = BlueprintObj->GetPackage();

        UE_LOG(LogBPPkg, Warning, TEXT("Load BP Name:%s Pkg:%s"), *BlueprintObj->GetName(), *Pkg->GetName());

        TArray<UObject*> PackageObjs;
        GetObjectsWithPackage(Pkg, PackageObjs);
        for (UObject* obj : PackageObjs)
        {
                UE_LOG(LogBPPkg, Warning, TEXT("        === %s : %s"), *obj->GetClass()->GetName(), *obj->GetName());
        }
}
输出结果为:


可以看出编辑器隐藏了不少对象,其中有:BlueprintGeneratedClass : TutorialCharacter_C即为该蓝图的UClass信息。并且下图中的蓝图组件也是有显示在上面的Log中:


如果对引擎底层创建蓝图对象的细节有兴趣可以查看NewObject<UBlueprintGeneratedClass>相关的代码或是以下函数:
UBlueprint* FKismetEditorUtilities::CreateBlueprint(UClass* ParentClass, UObject* Outer, const FName NewBPName, EBlueprintType BlueprintType, TSubclassOf<UBlueprint> BlueprintClassType, TSubclassOf<UBlueprintGeneratedClass> BlueprintGeneratedClassType, FName CallingContext)
4、如何销毁对象

// 针对AActor子类可调用
MyActor->Destroy();

// 针对UObject的子类,可调用
MyObj->MarkPendingKill();
MyObj = nullptr;
5、Transicent Package是什么?

Transicent Package就是用来存放临时对象的包,默认不指定Outer的都将放到这个Transcient包中。
定义的代码:
// Object manager internal variables.
/** Transient package.                                                                                                        */
static UPackage*                        GObjTransientPkg                                                                = NULL;               

UPackage* GetTransientPackage()
{
        return GObjTransientPkg;
}
初始化代码:
//
// Init the object manager and allocate tables.
//
void StaticUObjectInit()
{
        UObjectBaseInit();

        // Allocate special packages.
        GObjTransientPkg = NewObject<UPackage>(nullptr, TEXT("/Engine/Transient"), RF_Transient);
        GObjTransientPkg->AddToRoot(); //注意这里,将标记不被GC

        if( FParse::Param( FCommandLine::Get(), TEXT("VERIFYGC") ) )
        {
                GShouldVerifyGCAssumptions = true;
        }
        if( FParse::Param( FCommandLine::Get(), TEXT("NOVERIFYGC") ) )
        {
                GShouldVerifyGCAssumptions = false;
        }

        UE_LOG(LogInit, Log, TEXT("Object subsystem initialized") );
}
6、Outer怎么理解

最外层的Outer可以是UPackage,如果是上层对象可能是Persistent Level或是Sub Level,也可能是某个创建时指定的对象。
四、常见编程问题

1、如何让C++变量可以在蓝图中访问

UPROEPRTY中至少增加其中一项:BlueprintReadOnly,BlueprintReadWrite
2、如何让变量在各个属性编辑器中显示

添加其中一项标记:EditAnywhere、EditInstanceOnly、EditDefaultsOnly
显示控制标记为:VisibleAnywhere、VisibleInstanceOnly、VisibleDefaultsOnly
3、如何对象查找

// Find Actor by name (also works on UObjects)
AActor* MyActor = FindObject<AActor>(nullptr, TEXT("MyNamedActor"));

// Find Actors by type (needs a UWorld object)
for (TActorIterator<AMyActor> It(GetWorld()); It; ++It)
{
        AMyActor* MyActor = *It;
        // ...
}

// Find UObjects by type
for (TObjectIterator<UMyObject> It; It; ++It)
{
    UMyObject* MyObject = *It;
    // ...
}

// Find Actors by tag (also works on ActorComponents, use TObjectIterator instead)
for (TActorIterator<AActor> It(GetWorld()); It; ++It)
{
    AActor* Actor = *It;
    if (Actor->ActorHasTag(FName(TEXT("Mytag"))))
    {
        // ...
    }
}
4、如何资源引用

5、如何进行对象转型

使用Cast、ExactCast、CastChecked等模板函数
6、如何进行对象间通信(事件分派)

7、如何让一个属性编辑能根据条件进行显示

/** If true, WireframeColorOverride will be used. If false, color is determined based on mobility and physics simulation settings */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Rendering, meta=(InlineEditConditionToggle))
uint8 bOverrideWireframeColor:1;

/** Wireframe color to use if bOverrideWireframeColor is true */
UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadOnly, Category=Rendering, meta=(editcondition = "bOverrideWireframeColor"))
FColor WireframeColorOverride;
8、如何让对象不被GC掉

创建继承自UObject的对象后调用AddToRoot
        /**
         * Add an object to the root set. This prevents the object and all
         * its descendants from being deleted during garbage collection.
         */
        FORCEINLINE void AddToRoot()
        {
                GUObjectArray.IndexToObject(InternalIndex)->SetRootSet();
        }

        /** Remove an object from the root set. */
        FORCEINLINE void RemoveFromRoot()
        {
                GUObjectArray.IndexToObject(InternalIndex)->ClearRootSet();
        }

        /**
         * Returns true if this object is explicitly rooted
         *
         * @return true if the object was explicitly added as part of the root set.
         */
        FORCEINLINE bool IsRooted() const
        {
                return GUObjectArray.IndexToObject(InternalIndex)->IsRootSet();
        }
9、如何让对象被GC掉

首先确保没有被引用到:

  • 针对AActor子类可调用MyActor->Destroy();
  • 针对UObject的子类,可调用MyObj->MarkPendingKill(); MyObj = nullptr;
相应的函数有:ClearPendingKill、IsPendingKill
10、如何使用接口

TODO
11、蓝图BeginPlay事件是如何在C++哪里调用的?

参考AActor::BeginPlay代码中调用ReceiveBeginPlay相关实现:




似乎没有找到ReceiveBeginPlay的函数实现,继续看自动生成的代码(注意原厂编辑器发布时已经去掉了生成的cpp,下面的代码文件需要通过编译UE4源码产生):


扩展:在UE3及以前有UC(Unreal Script)脚本,其实就是现在的UPROPERTY、UFUNCTION、UCLASS、USTRUCT等C++宏定义的脚本语言形式,会通过UC脚本编译生成调用虚拟机的thunk代码。而以前的UC脚本对应的虚拟机也被蓝图的前身Kismet图形化节点编程用到,因此现在看到还有很多Kismet的命名与蓝图相关的C++代码,K2就是Kismet2(第2代)的缩写。(可图形化OOP的UE4 C++确实很酷)
五、常见使用问题

0、如何打开控制台输入框

快捷键:`  (数字键1左边的键)
1、在哪找LOG文件

所在工程目录下Saved\Logs目录中
2、如何看FPS


  • 编辑器或游戏中控制台输入:stat fps
  • 编辑器中还可以通过菜单打开(Ctrl+Shift+H)


3、如何看性能统计信息

可通过菜单或控制台命令:stat unit。如果要看更细的可以找子菜单Advanced中相应的类型,注意编辑器Output窗口会执行相应的Stat命令来切换显示,游戏中也可以用这些命令来查看,比如查看具体的渲染统计数据可用:stat D3D11RHI


4、夏天开着编辑器机器太烤脚,有何办法缓解?


  • 通过视图菜单关闭实时渲染(Ctrl+R)


5、如何在运行游戏时开启控制台窗口


  • 加命令行启动参数 -log
6、运行时如何修改分辨率


  • 使用命令:r.SetRes 640x480


7、启动时如何指定分辨率

加命令行参数:-ResX=1920 -ResY=1080 或 -res=1920x1080
窗口或全屏模式命令行参数:-windowed | -fullscreen | -res=1920x1080w | -res=1920x1080f | -res=1920x1080wf
参考:

  • bool ParseResolution(const TCHAR* InResolution, uint32& OutX, uint32& OutY, int32& OutWindowMode)
  • UEditorEngine::LaunchNewProcess
8、如何指定游戏窗口启动到另一个屏幕

TODO
9、如何启动DS服务器

set ue4="C:\Program Files\Epic Games\UE_4.27\Engine\Binaries\Win64\UE4Editor-Cmd.exe"
set prj="C:\UE4Projs\DemoReplication\DemoReplication.uproject"
set map=/Game/TopDownCPP/Maps/TopDownExampleMap?

%ue4%  %prj%  %map% -Server -log注意:其中Binaries\Win64下有对应的几个EXE,只有UE4Editor开头的几个EXE是可以通过工程文件运行的,其它的是要烘焙数据出包运行。
UE4Editor.exe、UE4Editor-Cmd.exe 需要编译:


UE4Editor-Win64-DebugGame.exe、UE4Editor-Win64-DebugGame-Cmd.exe 需要编译:


10、如何运行HOST服务器(带客户端的服务器,官方称为Listen Server)

主要就是将启动地图后面加Listen,并将-Server改为-Game,注意加-res显示为窗口模式
set ue4="C:\Program Files\Epic Games\UE_4.27\Engine\Binaries\Win64\UE4Editor-Cmd.exe"
set prj="C:\UE4Projs\DemoReplication\DemoReplication.uproject"
set map=/Game/TopDownCPP/Maps/TopDownExampleMap?Listen

%ue4%  %prj%  %map% -Game -log -res=1280x720w11、如何单独运行客户端游戏并连接DS服务器

第3个参数传ServerIP即可,如果有端口改变则为ServerIP:Port
set ue4="C:\Program Files\Epic Games\UE_4.27\Engine\Binaries\Win64\UE4Editor-Cmd.exe"
set prj="C:\UE4Projs\DemoReplication\DemoReplication.uproject"

%ue4%  %prj%  127.0.0.1 -Game -log -res=640x480w五、扩展阅读
回复

举报 使用道具

您需要登录后才可以回帖 登录 | 立即注册
快速回复 返回顶部 返回列表