编程和脚本编写

发布时间 2023-09-28 21:49:28作者: XTG111

编程指南

断言 assert

在开发期间帮助检测和诊断不正常或无效的运行时条件。且不会存在于发布代码中。其默认值必须一律为true。
assert主要有三个不同族系:check、verify和ensure,底层代码在Engine/Source/Runtime/Core/Public/Misc/AssertionMacros.h中。

check

最接近基础assert,第一个参数为false时,就会停止执行。
一般定义USE_CHECKS_IN_SHIPPING为1,其作用是:当怀疑check宏中的代码正在修改值;发现了仅存在于发布版本并且难以追踪的Bug,使用check宏可以找到它,发行版本将其值改为0

// 决不可使用空JumpTarget调用此函数。若发生此情况,须停止程序。
void AMyActor::CalculateJumpVelocity(AActor* JumpTarget, FVector& JumpVelocity)
{
    check(JumpTarget != nullptr);
    //(计算在JumpTarget上着陆所需的速度。现在可确定JumpTarget为非空。)
}

verify

其具体行为与check宏差不多,当表达式需要在发行版本中执行操作,应该使用verify。
若某个函数执行操作,然后返回 bool 来说明该操作是否成功,则应使用Verify而非Check来确保该操作成功。因为在发布版本中Verify将忽略返回值,但仍将执行操作。而Check在发布版本中根本不调用该函数。
定义 USE_CHECKS_IN_SHIPPING 来保留一个true值(通常为 1),从而覆盖此行为。在所有其他情况下,Verify宏将计算其表达式,但不会停止执行或将文本输出到日志。

// 这将设置Mesh的值,并预计为非空值。若之后Mesh的值为空,则停止程序。
// 使用Verify而非Check,因为表达式存在副作用(设置网格体)。
//Mesh = GetRenderMesh()这个在实际运行中需要操作
verify((Mesh = GetRenderMesh()) != nullptr);

ensure

一般用于出现非致命错误,虽然得到的是false值,但仍然可以继续运行

// 这行代码捕获了在产品发布版本中可能出现的小错误。
// 此错误较小,无需停止执行便可解决。
// 虽然该bug已修复,但开发者仍然希望了解之前是否曾经出现过此bug。
void AMyActor::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);
    // 确保bWasInitialized为true,然后再继续。若为false,则在日志中记录该bug尚未修复。
    if (ensureMsgf(bWasInitialized, TEXT("%s ran Tick() with bWasInitialized == false"), *GetActorLabel()))
    {
        //(执行一些需要已正确初始化AMyActor的操作。)
    }
}

异步资源加载

一般引用资源的方式是创建硬指针UProperty并为它指定一个类别,如果一个UObject指针引用了一个资源,应当在加载这个对象时,加载这个资源,而不是在游戏开始时加载全部资源。应当使用FSoftObjectPath或TSoftObjectPtr

资源注册表和对象库

资源注册表是用于存储资源元数据的系统,允许搜索和查询这些资源。对资源注册表的查询返回FAssetData类型的对象。
使用ObjectLibrary处理已卸载的资源组,ObjectLibrary是一个对象,它包含已加载对象列表或已卸载对象的FAssetData,这些对象都是从共享基类继承而来。通过为对象库指定搜索路径来加载对象库,它会将所有资源添加到该路径。
当找到一个需要的AssetData后,需要调用ToStringReference()将他转换为FSoftObjectPath,以便于后续的异步加载

StreamableManager和异步加载

利用StreamableManager可以简单的对上述FSoftObjectPath对象进行异步加载
其主要有两个函数
SynchronousLoad将进行一次简单的块加载并返回对象。该方法或许适用于较小对象,但可能会导致主线程长时间停滞。
RequestAsyncLoad将异步加载一组资源并在完成后调用委托

void UGameCheatManager::GrantItems()
{
       TArray<FSoftObjectPath> ItemsToStream;
       FStreamableManager& Streamable = UGameGlobals::Get().StreamableManager;
       for(int32 i = 0; i < ItemList.Num(); ++i)
       {
              ItemsToStream.AddUnique(ItemList[i].ToStringReference());
       }
       Streamable.RequestAsyncLoad(ItemsToStream, FStreamableDelegate::CreateUObject(this, &UGameCheatManager::GrantItemsDeferred));
}

void UGameCheatManager::GrantItemsDeferred()
{
       for(int32 i = 0; i < ItemList.Num(); ++i)
       {
              UGameItemData* ItemData = ItemList[i].Get();
              if(ItemData)
              {
                     MyPC->GrantItem(ItemData);
              }
       }
}

在这个示例中,ItemList是设计师在编辑器中修改过的TArray< TSoftObjectPtr >。代码迭代了这个列表,将其转换为FSoftObjectPath,并令其排队等候加载。所有这些项目加载完成(或由于缺少而加载失败)后,它调用传入的委托。然后,这个委托迭代同一个项目列表,取消引用,并将它们指定给一个玩家。StreamableManager在调用委托之前保持对其加载的任何资源的硬引用,因此在调用委托之前,不必担心想要异步加载的对象会被垃圾回收。它在调用委托后释放这些引用,因此如果仍希望保留引用,就需要在其他位置执行硬引用。

Core Redirects

主要解决重命名后,资源可能丢失的问题
对于项目中文件重命名,应当在'Default.ini'文件中配置,对于插件,应当在其独立的ini文件中配置,对于引擎的Paper2D插件,相应的文件为'BasePaper2D.ini'。在任何一种情况下,都需要将Core Redirects放置在"CoreRedirects"部分中。 加载资源时,这些Core Redirects将自动重新映射已过时数据,从而防止因重命名过程导致的数据丢失。
在CoreRedirects中指定类或结构体名称时,应当省略前缀如A,U,F等
ClassRedirects:更改对象和属性

[CoreRedirects] +ClassRedirects=(OldName="Pawn",NewName="MyPawn",InstanceOnly=true) +ClassRedirects=(OldName="/Script/MyModule.MyOldClass",NewName="/Script/MyModule.MyNewClass") +ClassRedirects=(OldName="PointLightComponent",NewName="PointLightComponent",ValueChanges=(("PointLightComponent0","LightComponent0"))) +ClassRedirects=(OldName="AnimNotify_PlayParticleEffect_C",NewName="/Script/Engine.AnimNotify_PlayParticleEffect",OverrideClassName="/Script/CoreUObject.Class")

EnumRedirects:重新映射列举类型中的已过时UENUM类型或者值
FunctionRedirects:更改已过时的UFUNCTION
PackageRedirects:从一个包重新映射到另一个包,或者禁止显示与对已删除包的引用相关的警告(引用将被清除或设置为空)
PropertyRedirects:将已删除属性重新映射到新属性
StructRedirects:更改属性(使用已过时或已删除USTRUCT引用新USTRUCT)

引用资源

两种引用方式

  • 硬性引用即对象 A 引用对象 B,并导致对象 B 在对象 A 加载时加载
  • 软性引用,即对象 A 通过间接机制(例如字符串形式的对象路径)来引用对象 B

硬性引用

直接属性引用:最常见的引用方式,并且通过UPROPERTY公开。

UPROPERTY(EditDefaultsOnly, Category=Building)
USoundCue* ConstructionStartStinger;

上述代码相当于对于每一个新类都会加载SoundCue

构造时引用:一般当我们知道需要为给定属性加载确切的资源时应用,并在对象的构造中设置该属性。需要使用ConstructorHelpers类完成
下面是一个HUD的构造函数

UPROPERTY()
class UTexture2D* BarFillTexture;
AStrategyHUD::AStrategyHUD(const FObjectInitializer& ObjectInitializer) :
    Super(ObjectInitializer)
{
    static ConstructorHelpers::FObjectFinder<UTexture2D> BarFillObj(TEXT("/Game/UI/HUD/BarFill"));
    ...
    BarFillTexture = BarFillObj.Object;
    ...
}

ConstructorHelpers去寻找资源按照路径,然后加载。使用资源的完整路径来指定要加载的内容。如果该资源不存在或者由于出错而无法加载,那么该属性将设置为 nullptr
上述代码相当于在构造HUD时,应用/Game/UI/HUD/BarFill位置处的资源构建BarFillTexture

软性引用

间接属性引用
TSoftObjectPtr:控制何时加载资源,间接属性引用的工作方式和直接引用一样,但属性以字符串形式与模版代码存储在一起以便安全地检查资源是否已加载,而不是进行直接指针引用。通常使用IsPending()方法检查资源是否可以访问。

UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category=Building)
TSoftObjectPtr<UStaticMesh> BaseMesh;

UStaticMesh* GetLazyLoadedMesh()
{
    if (BaseMesh.IsPending())
    {
        const FSoftObjectPath& AssetRef = BaseMesh.ToStringReference();
        BaseMesh = Cast< UStaticMesh>(Streamable.SynchronousLoad(AssetRef));
    }
    return BaseMesh.Get();
}

利用TSoftObjectPtr将BaseMesh的构建延后,当要加载时,先检测BaseMesh.IsPending,如果没有加载,使用FStreamingManager异步加载。之后将值以指针的形式返回给调用者GetLazyLoadedMesh。

查找/加载对象
上述对象的引用加载全是需要依靠UPROPERTY来定义说明的,如何直接利用字符串即资源位置来引用对象?
如果只在当一个UObject已经加载或创建才引用它,使用FindObject<>();

AFunctionalTest* TestToRun = FindObject<AFunctionalTest>(TestsOuter, *TestName);

在TestsOuter的package下寻找已经加载了的名字为TestName的对象

如果未被加载但希望引用,使用LoadObject<>();

GridTexture = LoadObject<UTexture2D>(NULL, TEXT("/Engine/EngineMaterials/DefaultWhiteGrid.DefaultWhiteGrid"), NULL, LOAD_None, NULL);

加载"/Engine/EngineMaterials/DefaultWhiteGrid.DefaultWhiteGrid"对象到GridTexture