天天看点

UE4入门序列02(UE4的命名规则)

#1 为什么要有命名规则

#2 C++类相关命名规则

#3 资源的命名规则

#4 项目目录的结构

#1 为什么要有命名规则

  1. 在我们开放软件的过程中,有80%的时间的是花费在维护软件的时间上面;
  2. 通常一个软件的维护生命周期非常长,中途更换的人会很多;
  3. 好的命名规范可以加速软件的可阅读能力,加速软件的新功能开发;
  4. 如果哪一天当我们想开源的时候,好的命名开源更容易让其他开发者快速理解;
  5. 大多数我们开发的功能都是跨平台的,需要制定一个命名规范;

所以我们需要有一个好的命名规范来统一源代码,各种资源,目录结构等。

#2 C++类相关命名规则

版权声明

所有Epic公司的源代码包括开源的代码(.h, .cpp, .xaml)在文件的头部位置会有

// Copyright Epic Games, Inc. All Rights Reserved.

如果你改了源码的版权声明,编码是无法通过的!

类名命名规则

UE中创建的C++类会自己给类名添加一个前缀,通常有

A, U, F

,各有不同的含义:

  1. 模板类的前缀为

    T

  2. UObject

    继承的前缀为

    U

  3. AActor

    继承的前缀为

    A

  4. SWidget

    继承的前缀为

    S

  5. Abstract Class

    继承的前缀为

    I

  6. Enums

    的前缀为

    E

  7. boolean

    的前缀为

    b

    ,比如

    bFaded、bLogined

  8. 大多数类会有

    F

    的前缀,表示这是一个

    Framework

  9. Typedef

    应该拥有前缀暴露类型,

    F

    struct

    Typedef

    的前缀,

    U

    UObject

    的前缀
  10. typedef

    不算是模板实例,应该使用前缀声明,比如

    typedef TArray<FMytype> FArrayOfMyType

  11. UnrealHeaderTool会自动检查声明的前缀

变量、方法和类名称应该是清晰的、明确的和描述性的。名称的范围越大,一个好的、描述性的名称就越重要。避免over-abbreviation。

所有变量都应该一次声明一个,这样就可以提供对变量含义的注释。而且,JavaDocs风格也需要它。可以在变量前使用多行或单行注释,对于分组变量,空行是可选的。

所有返回bool值的函数都应该问是否正确的问题,例如IsVisible()或ShouldClearBuffer()。

一个过程(没有返回值的函数)应该使用一个强谓词,后跟一个对象。如果方法的对象是它所在的对象,则是异常。那么对象就是根据上下文来理解的。要避免的名字包括那些以“Handle”和“Process”开头的,因为这些动词是不明确的。

虽然不是必需的,但是我们鼓励您在函数参数名前面加上“Out”,如果它们是通过引用传递的,并且函数希望写入这个值。很明显,在这个参数中传递的值将被函数替换。

如果一个In或Out参数也是一个布尔值,将“b”放在In/Out前缀之前,例如bOutResult。

返回值的函数应该描述返回值。函数名应该清楚地表明函数将返回什么值。这对于布尔函数特别重要。考虑以下两个示例方法:

// what does true mean?
bool CheckTea(FTea Tea);

// name makes it clear true means tea is fresh
bool IsTeaFresh(FTea Tea);
           
float TeaWeight;
int32 TeaCount;
bool bDoesTeaStink;
FName TeaName;
FString TeaFriendlyName;
UClass* TeaClass;
USoundCue* TeaSound;
UTexture* TeaTexture;
           

C++编码协议

bool

for boolean values

(NEVER assume the size of bool). BOOL will not compile

.

TCHAR

for a character

(NEVER assume the size of TCHAR)

.

PTRINT

for an integer that may hold a pointer

(NEVER assume the size of PTRINT)

.

uint8

for unsigned bytes (1 byte).

int8

for signed bytes (1 byte).

uint16

for unsigned “shorts” (2 bytes).

int16

for signed “shorts” (2 bytes).

uint32

for unsigned ints (4 bytes).

int32

for signed ints (4 bytes).

uint64

for unsigned “quad words” (8 bytes).

int64

for signed “quad words” (8 bytes).

float

for single precision floating point (4 bytes).

double

for double precision floating point (8 bytes).

使用UE提供的编码协议,方便跨平台。

C++标准库

历史上,UE一直避免直接使用C和c++标准库。这有几个原因,包括:用我们自己的实现取代缓慢的实现,允许对内存分配进行额外的控制,在广泛使用之前添加新功能,做出令人满意但不规范的行为改变,在代码库中拥有一致的语法,或避免与UE习惯用法不兼容的结构。然而,近年来,标准库已经变得更加稳定和成熟,并且包含了我们不想用抽象层包装或自己重新实现的功能。

如果希望使用以前没有使用过的新标准库组件,那么应该向编码标准组提出要求进行评估。这也允许我们在被接受时保持这个白名单组件列表是最新的。

当需要在标准库特性而不是我们自己的特性之间做出选择时,最好选择能提供更好结果的选项,但记住一致性也很重要。如果一个遗留的UE实现不再有任何用途,我们可以选择弃用它,并将其全部使用迁移到标准库。

避免在同一个API中混合使用UE和标准库。

  1. 应该在新代码中使用,旧代码迁移时使用。Atomics有望在所有受支持的平台上全面而高效地实现。我们自己的TAtomic只是部分实现了,维护和改进它并不符合我们的利益。
  2. 应该在传统UE特征和标准特征有重叠的地方使用。特征通常作为编译器内在的正确性实现,编译器可以了解标准特征,并选择更快的编译路径,而不是把它们当作普通的c++。一个问题是,我们的特征通常有一个大写的值static或Type typedef,而标准特征被期望使用Value和Type。这是一个重要的区别,因为一个特定的语法是由组合特征所期望的,例如std::连词。我们添加的新traits应该用小写的值或类型来支持组合,并且现有的traits应该被更新来支持这两种情况。
  3. 必须用于支持花括号的初始化器语法。这是一种语言和标准库重叠的情况,如果您想支持它,就没有其他选择。
  4. 可以直接使用,但它的使用应该封装在编辑器代码中。我们没有计划实现我们自己的regex解决方案。
  5. std::numeric_limits可以完整地使用。
  6. 只有这个头文件中的浮点数比较函数可以使用,如下所示。

除了在互操作代码中,应该避免使用标准容器和字符串。

const

关键字使用

const

可以精准的描述数据和数据之间的关系,约束数据的作用域;

void SomeMutatingOperation(FThing& OutResult, const TArray<Int32>& InArray)
{
    // InArray will not be modified here, but OutResult probably will be
}

void FThing::SomeNonMutatingOperation() const
{
    // This code will not modify the FThing it is invoked on
}

TArray<FString> StringArray;
for (const FString& : StringArray)
{
    // The body of this loop will not modify StringArray
}
           
void AddSomeThings(const int32 Count);

void AddSomeThings(const int32 Count)
{
    const int32 CountPlusOne = Count + 1;
    // Neither Count nor CountPlusOne can be changed during the body of the function
}
           
void FBlah::SetMemberArray(TArray<FString> InNewArray)
{
    MemberArray = MoveTemp(InNewArray);
}
           
// Const pointer to non-const object - pointer cannot be reassigned, but T can still be modified
T* const Ptr = ...;

// Illegal
T& const Ref = ...;
           
// Bad - returning a const array
const TArray<FString> GetSomeArray();

// Fine - returning a reference to a const array
const TArray<FString>& GetSomeArray();

// Fine - returning a pointer to a const array
const TArray<FString>* GetSomeArray();

// Bad - returning a const pointer to a const array
const TArray<FString>* const GetSomeArray();
           

代码格式

我们使用一个基于JavaDoc的系统从代码和构建文档中自动提取注释,因此需要遵循一些特定的注释格式规则。

下面的示例演示类注释、方法注释和变量注释的格式。记住,注释应该扩展代码。代码记录实现,注释记录意图。请确保在更改一段代码的意图时更新注释。

注意,这里支持两种不同的参数注释样式,由陡峭和Sweeten方法显示。陡峭使用的@param风格是传统的多行风格,但对于简单函数,将参数和返回值文档集成到函数的描述性注释中会更清晰,就像sweet示例中那样。像@see或@return这样的特殊注释标签应该只用于在主描述后开始新行。

方法注释应该只包含一次,在公开声明方法的地方。方法注释应该只包含与方法调用方相关的信息,包括可能与调用方相关的方法覆盖的任何信息。关于方法的实现及其重写的细节(与调用者无关)应该在方法实现中注释。

/** The interface for drinkable objects. */
class IDrinkable
{
public:
    /**
     * Called when a player drinks this object.
     * @param OutFocusMultiplier - Upon return, will contain a multiplier to apply to the drinker's focus.
     * @param OutThirstQuenchingFraction - Upon return, will contain the fraction of the drinker's thirst to quench (0-1).
     * @warning Only call this after the drink has been properly prepared.     
     */
    virtual void Drink(float& OutFocusMultiplier, float& OutThirstQuenchingFraction) = 0;
};

/** A single cup of tea. */
class FTea : public IDrinkable
{
public:
    /**
     * Calculate a delta-taste value for the tea given the volume and temperature of water used to steep.
     * @param VolumeOfWater - Amount of water used to brew in mL
     * @param TemperatureOfWater - Water temperature in Kelvins
     * @param OutNewPotency - Tea's potency after steeping starts, from 0.97 to 1.04
     * @return The change in intensity of the tea in tea taste units (TTU) per minute
     */
    float Steep(
        const float VolumeOfWater,
        const float TemperatureOfWater,
        float& OutNewPotency
    );

    /** Adds a sweetener to the tea, quantified by the grams of sucrose that would produce the same sweetness. */
    void Sweeten(const float EquivalentGramsOfSucrose);

    /** The value in yen of tea sold in Japan. */
    float GetPrice() const
    {
        return Price;
    }

    virtual void Drink(float& OutFocusMultiplier, float& OutThirstQuenchingFraction) override;

private:
    /** Price in Yen */
    float Price;

    /** Current level of sweet, in equivalent grams of sucrose */
    float Sweetness;
};

float FTea::Steep(const float VolumeOfWater, const float TemperatureOfWater, float& OutNewPotency)
{
    ...
}

void FTea::Sweeten(const float EquivalentGramsOfSucrose)
{
    ...
}

void FTea::Drink(float& OutFocusMultiplier, float& OutThirstQuenchingFraction)
{
    ...
}
           

新C++格式

TMap<FString, int32> MyMap;

// Old style
for (auto It = MyMap.CreateIterator(); It; ++It)
{
    UE_LOG(LogCategory, Log, TEXT("Key: %s, Value: %d"), It.Key(), *It.Value());
}

// New style
for (TPair<FString, int32>& Kvp : MyMap)
{
    UE_LOG(LogCategory, Log, TEXT("Key: %s, Value: %d"), *Kvp.Key, Kvp.Value);
}
           
// Old style
for (TFieldIterator<UProperty> PropertyIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt)
{
    UProperty* Property = *PropertyIt;
    UE_LOG(LogCategory, Log, TEXT("Property name: %s"), *Property->GetName());
}

// New style
for (UProperty* Property : TFieldRange<UProperty>(InStruct, EFieldIteratorFlags::IncludeSuper))
{
    UE_LOG(LogCategory, Log, TEXT("Property name: %s"), *Property->GetName());
}
           
// Find first Thing whose name contains the word "Hello"
Thing* HelloThing = ArrayOfThings.FindByPredicate([](const Thing& Th){ return Th.GetName().Contains(TEXT("Hello")); });

// Sort array in reverse order of name
Algo::Sort(ArrayOfThings, [](const Thing& Lhs, const Thing& Rhs){ return Lhs.GetName() > Rhs.GetName(); });
           
// Without the return type here, the return type is unclear
auto Lambda = []() -> FMyType
{
    return SomeFunc();
}
           
TUniquePtr<FThing> ThingPtr = MakeUnique<FThing>();
AsyncTask([UniquePtr = MoveTemp(UniquePtr)]()
{
    // Use UniquePtr here
});
           
// Old enum
UENUM()
namespace EThing
{
    enum Type
    {
        Thing1,
        Thing2
    };
}

// New enum
UENUM()
enum class EThing : uint8
{
    Thing1,
    Thing2
}
           
// Old property
UPROPERTY()
TEnumAsByte<EThing::Type> MyProperty;

// New property
UPROPERTY()
EThing MyProperty;
           
enum class EFlags
{
    None = 0x00,
    Flag1 = 0x01,
    Flag2 = 0x02,
    Flag3 = 0x04
};

ENUM_CLASS_FLAGS(EFlags)
           
// Old
if (Flags & EFlags::Flag1)

// New
if ((Flags & EFlags::Flag1) != EFlags::None)
           

其他编码格式

UCLASS()
class UTeaOptions : public UObject
{
    GENERATED_BODY()

public:
    UPROPERTY()
    int32 MaximumNumberOfCupsPerDay = 10;

    UPROPERTY()
    float CupWidth = 11.5f;

    UPROPERTY()
    FString TeaType = TEXT("Earl Grey");

    UPROPERTY()
    EDrinkingStyle DrinkingStyle = EDrinkingStyle::PinkyExtended;
};
           
// @third party code - BEGIN PhysX
#include <physx.h>
// @third party code - END PhysX
// @third party code - BEGIN MSDN SetThreadName
// [http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx]
// Used to set the thread name in the debugger
...
//@third party code - END MSDN SetThreadName
           
if (bThing)
{
    return;
}
           
if (bHaveUnrealLicense)
{
    InsertYourGameHere();
}
else
{
    CallMarkRein();
}
           
if (TannicAcid < 10)
{
    UE_LOG(LogCategory, Log, TEXT("Low Acid"));
}
else if (TannicAcid < 100)
{
    UE_LOG(LogCategory, Log, TEXT("Medium Acid"));
}
else
{
    UE_LOG(LogCategory, Log, TEXT("High Acid"));
}
           
switch (condition)
{
    case 1:
        ...
        // falls through

    case 2:
        ...
        break;

    case 3:
        ...
        return;

    case 4:
    case 5:
        ...
        break;

    default:
        break;
}
           
#pragma once
//<file contents>
           
if ((Blah->BlahP->WindowExists->Etc && Stuff) &&
    !(bPlayerExists && bGameStarted && bPlayerStillHasPawn &&
    IsTuesday())))
{
    DoSomething();
}

should be replaced with
const bool bIsLegalWindow = Blah->BlahP->WindowExists->Etc && Stuff;
const bool bIsPlayerDead = bPlayerExists && bGameStarted && bPlayerStillHasPawn && IsTuesday();
if (bIsLegalWindow && !bIsPlayerDead)
{
    DoSomething();
}
           
FShaderType* Ptr

Not these:
FShaderType *Ptr
FShaderType * Ptr
           
class FSomeClass
{
public:
    void Func(const int32 Count)
    {
        for (int32 Count = 0; Count != 10; ++Count)
        {
            // Use Count
        }
    }

private:
    int32 Count;
}
           
// Old style
Trigger(TEXT("Soldier"), 5, true);.

// New style
const FName ObjectName                = TEXT("Soldier");
const float CooldownInSeconds         = 5;
const bool bVulnerableDuringCooldown  = true;
Trigger(ObjectName, CooldownInSeconds, bVulnerableDuringCooldown);
           
// Old style
FCup* MakeCupOfTea(FTea* Tea, bool bAddSugar = false, bool bAddMilk = false, bool bAddHoney = false, bool bAddLemon = false);
FCup* Cup = MakeCupOfTea(Tea, false, true, true);

// New style
enum class ETeaFlags
{
    None,
    Milk  = 0x01,
    Sugar = 0x02,
    Honey = 0x04,
    Lemon = 0x08
};
ENUM_CLASS_FLAGS(ETeaFlags)

FCup* MakeCupOfTea(FTea* Tea, ETeaFlags Flags = ETeaFlags::None);
FCup* Cup = MakeCupOfTea(Tea, ETeaFlags::Milk | ETeaFlags::Honey);
           
// Old style
TUniquePtr<FCup[]> MakeTeaForParty(const FTeaFlags* TeaPreferences, uint32 NumCupsToMake, FKettle* Kettle, ETeaType TeaType = ETeaType::EnglishBreakfast, float BrewingTimeInSeconds = 120.0f);

// New style
struct FTeaPartyParams
{
    const FTeaFlags* TeaPreferences       = nullptr;
    uint32           NumCupsToMake        = 0;
    FKettle*         Kettle               = nullptr;
    ETeaType         TeaType              = ETeaType::EnglishBreakfast;
    float            BrewingTimeInSeconds = 120.0f;
};
TUniquePtr<FCup[]> MakeTeaForParty(const FTeaPartyParams& Params);
           
void Func(const FString& String);
void Func(bool bBool);

Func(TEXT("String")); // Calls the bool overload!
           
class A
{
public:
    virtual void F() {}
};

class B : public A
{
public:
    virtual void F() override;
}
           

#3 资源的命名规则

UE4入门序列02(UE4的命名规则)
UE4入门序列02(UE4的命名规则)
UE4入门序列02(UE4的命名规则)
UE4入门序列02(UE4的命名规则)
UE4入门序列02(UE4的命名规则)
UE4入门序列02(UE4的命名规则)
UE4入门序列02(UE4的命名规则)
UE4入门序列02(UE4的命名规则)
UE4入门序列02(UE4的命名规则)

#4 项目目录的结构

UE4入门序列02(UE4的命名规则)

附蓝图美化

UE4入门序列02(UE4的命名规则)

参考资料:

Unreal API

UE Style

上一篇: UE4的UE_LOG
下一篇: UE4编码规范

继续阅读