天天看點

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編碼規範

繼續閱讀